1use std::{env, path::PathBuf, process, str::FromStr, sync::LazyLock};
2
3use clap::Args;
4pub use toml::Value;
5use tracing::Level;
6
7use crate::{IncludePattern, LeanMode, OverflowMode, PointerWidth, RawDerefMode, SmtSolver};
8
9const FLUX_FLAG_PREFIX: &str = "-F";
10
11macro_rules! flux_arg {
12 ($name:literal) => {
13 concat!("F", $name)
14 };
15}
16
17pub const EXIT_FAILURE: i32 = 2;
19
20#[derive(Args)]
21#[command(next_help_heading = "Flux-Specific Flags (Not Yet Supported)")]
22pub struct Flags {
23 #[arg(
25 long = flux_arg!("log-dir"),
26 value_name = "PATH",
27 default_value = "./log/",
28 )]
29 pub log_dir: PathBuf,
30 #[arg(
32 long = flux_arg!("lean-dir"),
33 value_name = "PATH",
34 default_value = "./"
35 )]
36 pub lean_dir: PathBuf,
37 #[arg(
39 long = flux_arg!("lean-project"),
40 value_name = "NAME",
41 default_value = "lean_proofs"
42 )]
43 pub lean_project: String,
44 #[arg(long = flux_arg!("include"), value_name = "PATTERN", value_parser = panicking_parser)]
46 pub include: Option<IncludePattern>,
47 #[arg(long = flux_arg!("include-trusted"), value_name = "PATTERN", value_parser = panicking_parser)]
49 pub include_trusted: Option<IncludePattern>,
50 #[arg(
52 long = flux_arg!("include-trusted-impl"),
53 value_name = "PATTERN",
54 value_parser = panicking_parser
55 )]
56 pub include_trusted_impl: Option<IncludePattern>,
57 #[arg(
60 long = flux_arg!("pointer-width"),
61 value_name = "WIDTH",
62 default_value = "64",
63 value_parser = default_pointerwidth
64 )]
65 pub pointer_width: PointerWidth,
66 #[arg(long = flux_arg!("cache"), value_name = "PATH", value_parser = panicking_parser)]
68 pub cache: Option<PathBuf>,
69 #[arg(
71 long = flux_arg!("annots"),
72 num_args = 0..=1,
73 default_missing_value = "true"
74 )]
75 pub annots: bool,
76 #[arg(
79 long = flux_arg!("timings"),
80 num_args = 0..=1,
81 default_missing_value = "true"
82 )]
83 pub timings: bool,
84 #[arg(
86 long = flux_arg!("summary"),
87 num_args = 0..=1,
88 default_missing_value = "true"
89 )]
90 pub summary: bool,
91 #[arg(
93 long = flux_arg!("solver"),
94 value_name = "SOLVER",
95 default_value = "z3",
96 value_parser = default_smtsolver
97 )]
98 pub solver: SmtSolver,
99 #[arg(
101 long = flux_arg!("scrape-quals"),
102 num_args = 0..=1,
103 default_missing_value = "true"
104 )]
105 pub scrape_quals: bool,
106 #[arg(
108 long = flux_arg!("allow-uninterpreted-cast"),
109 num_args = 0..=1,
110 default_missing_value = "true"
111 )]
112 pub allow_uninterpreted_cast: bool,
113 #[arg(
116 long = flux_arg!("smt-define-fun"),
117 num_args = 0..=1,
118 default_missing_value = "true"
119 )]
120 pub smt_define_fun: bool,
121 #[arg(
125 long = flux_arg!("check-overflow"),
126 value_name = "MODE",
127 default_value = "none",
128 value_parser = default_overflowmode
129 )]
130 pub check_overflow: OverflowMode,
131 #[arg(
133 long = flux_arg!("allow-raw-deref"),
134 value_name = "MODE",
135 default_value = "default",
136 value_parser = default_rawderefmode
137 )]
138 pub allow_raw_deref: RawDerefMode,
139 #[arg(
141 long = flux_arg!("dump-constraint"),
142 num_args = 0..=1,
143 default_missing_value = "true"
144 )]
145 pub dump_constraint: bool,
146 #[arg(long = flux_arg!("dump-checker-trace"), value_name = "LEVEL", value_parser = panicking_parser)]
148 pub dump_checker_trace: Option<tracing::Level>,
149 #[arg(
151 long = flux_arg!("dump-fhir"),
152 num_args = 0..=1,
153 default_missing_value = "true"
154 )]
155 pub dump_fhir: bool,
156 #[arg(
158 long = flux_arg!("dump-rty"),
159 num_args = 0..=1,
160 default_missing_value = "true"
161 )]
162 pub dump_rty: bool,
163 #[arg(
165 long = flux_arg!("catch-bugs"),
166 num_args = 0..=1,
167 default_missing_value = "true"
168 )]
169 pub catch_bugs: bool,
170 #[arg(
174 long = flux_arg!("verify"),
175 num_args = 0..=1,
176 default_missing_value = "false"
177 )]
178 pub verify: bool,
179 #[arg(
182 long = flux_arg!("full-compilation"),
183 num_args = 0..=1,
184 default_missing_value = "true"
185 )]
186 pub full_compilation: bool,
187 #[arg(long = flux_arg!("sysroot"), value_name = "PATH", value_parser = panicking_parser)]
189 pub sysroot: Option<PathBuf>,
190 #[arg(
192 long = flux_arg!("trusted"),
193 num_args = 0..=1,
194 default_missing_value = "false"
195 )]
196 pub trusted_default: bool,
197 #[arg(
199 long = flux_arg!("ignore"),
200 num_args = 0..=1,
201 default_missing_value = "false"
202 )]
203 pub ignore_default: bool,
204 #[arg(
205 long = flux_arg!("lean"),
206 value_name = "MODE",
207 default_value = "default",
208 value_parser = default_leanmode
209 )]
210 pub lean: LeanMode,
211 #[arg(
213 long = flux_arg!("no-panic"),
214 num_args = 0..=1,
215 default_missing_value = "false"
216 )]
217 pub no_panic: bool,
218 #[arg(
221 long = flux_arg!("std-extern-specs"),
222 num_args = 0..=1,
223 default_missing_value = "false"
224 )]
225 pub std_extern_specs: bool,
226 #[arg(
228 long = flux_arg!("flux-verbose"),
229 num_args = 0..=1,
230 default_missing_value = "false"
231 )]
232 pub flux_verbose: bool,
233 #[arg(
235 long = flux_arg!("no-suggestions"),
236 num_args = 0..=1,
237 default_missing_value = "false"
238 )]
239 pub no_suggestions_default: bool,
240 #[arg(
243 long = flux_arg!("rerun-hint"),
244 num_args = 0..=1,
245 default_missing_value = "true"
246 )]
247 pub rerun_hint: bool,
248}
249
250impl Default for Flags {
251 fn default() -> Self {
252 Self {
253 log_dir: PathBuf::from("./log/"),
254 lean_dir: PathBuf::from("./"),
255 lean_project: "lean_proofs".to_string(),
256 dump_constraint: false,
257 dump_checker_trace: None,
258 dump_fhir: false,
259 dump_rty: false,
260 catch_bugs: false,
261 pointer_width: PointerWidth::default(),
262 include: None,
263 include_trusted: None,
264 include_trusted_impl: None,
265 cache: None,
266 check_overflow: OverflowMode::default(),
267 allow_raw_deref: RawDerefMode::default(),
268 scrape_quals: false,
269 allow_uninterpreted_cast: false,
270 solver: SmtSolver::default(),
271 smt_define_fun: false,
272 annots: false,
273 timings: false,
274 summary: true,
275 verify: false,
276 full_compilation: false,
277 sysroot: None,
278 trusted_default: false,
279 ignore_default: false,
280 lean: LeanMode::default(),
281 no_panic: false,
282 std_extern_specs: false,
283 flux_verbose: false,
284 no_suggestions_default: false,
285 rerun_hint: true,
286 }
287 }
288}
289
290pub(crate) static FLAGS: LazyLock<Flags> = LazyLock::new(|| {
291 let mut flags = Flags::default();
292 let mut includes: Vec<String> = Vec::new();
293 let mut trusteds: Vec<String> = Vec::new();
294 let mut trusted_impls: Vec<String> = Vec::new();
295 for arg in env::args() {
296 let Some((key, value)) = parse_flux_arg(&arg) else { continue };
297
298 let result = match key {
299 "log-dir" => parse_path_buf(&mut flags.log_dir, value),
300 "lean-dir" => parse_path_buf(&mut flags.lean_dir, value),
301 "lean-project" => parse_string(&mut flags.lean_project, value),
302 "dump-constraint" => parse_bool(&mut flags.dump_constraint, value),
303 "dump-checker-trace" => parse_opt_level(&mut flags.dump_checker_trace, value),
304 "dump-fhir" => parse_bool(&mut flags.dump_fhir, value),
305 "dump-rty" => parse_bool(&mut flags.dump_rty, value),
306 "catch-bugs" => parse_bool(&mut flags.catch_bugs, value),
307 "pointer-width" => parse_pointer_width(&mut flags.pointer_width, value),
308 "check-overflow" => parse_overflow(&mut flags.check_overflow, value),
309 "allow-raw-deref" => parse_raw_deref(&mut flags.allow_raw_deref, value),
310 "scrape-quals" => parse_bool(&mut flags.scrape_quals, value),
311 "allow-uninterpreted-cast" => parse_bool(&mut flags.allow_uninterpreted_cast, value),
312 "solver" => parse_solver(&mut flags.solver, value),
313 "smt-define-fun" => parse_bool(&mut flags.smt_define_fun, value),
314 "annots" => parse_bool(&mut flags.annots, value),
315 "timings" => parse_bool(&mut flags.timings, value),
316 "summary" => parse_bool(&mut flags.summary, value),
317 "cache" => parse_opt_path_buf(&mut flags.cache, value),
318 "include" => parse_opt_include(&mut includes, value),
319 "include-trusted" => parse_opt_include(&mut trusteds, value),
320 "include-trusted-impl" => parse_opt_include(&mut trusted_impls, value),
321 "verify" => parse_bool(&mut flags.verify, value),
322 "full-compilation" => parse_bool(&mut flags.full_compilation, value),
323 "sysroot" => parse_opt_path_buf(&mut flags.sysroot, value),
324 "trusted" => parse_bool(&mut flags.trusted_default, value),
325 "ignore" => parse_bool(&mut flags.ignore_default, value),
326 "lean" => parse_lean_mode(&mut flags.lean, value),
327 "no-panic" => parse_bool(&mut flags.no_panic, value),
328 "std-extern-specs" => parse_bool(&mut flags.std_extern_specs, value),
329 "flux-verbose" => parse_bool(&mut flags.flux_verbose, value),
330 "no-suggestions" => parse_bool(&mut flags.no_suggestions_default, value),
331 "rerun-hint" => parse_bool(&mut flags.rerun_hint, value),
332 _ => {
333 eprintln!("error: unknown flux option: `{key}`");
334 process::exit(EXIT_FAILURE);
335 }
336 };
337 if let Err(reason) = result {
338 eprintln!("error: incorrect value for flux option `{key}` - `{reason}`");
339 process::exit(1);
340 }
341 }
342 if !includes.is_empty() {
343 let include = IncludePattern::new(includes).unwrap_or_else(|err| {
344 eprintln!("error: invalid include pattern: {err}");
345 process::exit(1);
346 });
347 flags.include = Some(include);
348 }
349 if !trusteds.is_empty() {
350 let trusted = IncludePattern::new(trusteds).unwrap_or_else(|err| {
351 eprintln!("error: invalid trusted pattern: {err}");
352 process::exit(1);
353 });
354 flags.include_trusted = Some(trusted);
355 }
356 if !trusted_impls.is_empty() {
357 let trusted_impl = IncludePattern::new(trusted_impls).unwrap_or_else(|err| {
358 eprintln!("error: invalid trusted-impl pattern: {err}");
359 process::exit(1);
360 });
361 flags.include_trusted_impl = Some(trusted_impl);
362 }
363 flags
364});
365
366pub fn is_flux_arg(arg: &str) -> bool {
367 parse_flux_arg(arg).is_some()
368}
369
370fn parse_flux_arg(arg: &str) -> Option<(&str, Option<&str>)> {
371 let arg = arg.strip_prefix(FLUX_FLAG_PREFIX)?;
372 if arg.is_empty() {
373 return None;
374 }
375 if let Some((k, v)) = arg.split_once('=') { Some((k, Some(v))) } else { Some((arg, None)) }
376}
377
378fn parse_bool(slot: &mut bool, v: Option<&str>) -> Result<(), &'static str> {
379 match v {
380 Some("y") | Some("yes") | Some("on") | Some("true") | None => {
381 *slot = true;
382 Ok(())
383 }
384 Some("n") | Some("no") | Some("off") | Some("false") => {
385 *slot = false;
386 Ok(())
387 }
388 _ => {
389 Err(
390 "expected no value or one of `y`, `yes`, `on`, `true`, `n`, `no`, `off`, or `false`",
391 )
392 }
393 }
394}
395
396fn parse_string(slot: &mut String, v: Option<&str>) -> Result<(), &'static str> {
397 match v {
398 Some(s) => {
399 *slot = s.to_string();
400 Ok(())
401 }
402 None => Err("expected a string"),
403 }
404}
405
406fn parse_path_buf(slot: &mut PathBuf, v: Option<&str>) -> Result<(), &'static str> {
407 match v {
408 Some(s) => {
409 *slot = PathBuf::from(s);
410 Ok(())
411 }
412 None => Err("expected a path"),
413 }
414}
415
416fn parse_pointer_width(slot: &mut PointerWidth, v: Option<&str>) -> Result<(), &'static str> {
417 match v {
418 Some(s) => {
419 *slot = s.parse()?;
420 Ok(())
421 }
422 _ => Err(PointerWidth::ERROR),
423 }
424}
425
426fn parse_lean_mode(slot: &mut LeanMode, v: Option<&str>) -> Result<(), &'static str> {
427 match v {
428 Some(s) => {
429 *slot = s.parse()?;
430 Ok(())
431 }
432 _ => Err(LeanMode::ERROR),
433 }
434}
435
436fn parse_overflow(slot: &mut OverflowMode, v: Option<&str>) -> Result<(), &'static str> {
437 match v {
438 Some(s) => {
439 *slot = s.parse()?;
440 Ok(())
441 }
442 _ => Err(OverflowMode::ERROR),
443 }
444}
445
446fn parse_raw_deref(slot: &mut RawDerefMode, v: Option<&str>) -> Result<(), &'static str> {
447 match v {
448 Some(s) => {
449 *slot = s.parse()?;
450 Ok(())
451 }
452 _ => Err(RawDerefMode::ERROR),
453 }
454}
455
456fn parse_solver(slot: &mut SmtSolver, v: Option<&str>) -> Result<(), &'static str> {
457 match v {
458 Some(s) => {
459 *slot = s.parse()?;
460 Ok(())
461 }
462 _ => Err(SmtSolver::ERROR),
463 }
464}
465
466fn parse_opt_path_buf(slot: &mut Option<PathBuf>, v: Option<&str>) -> Result<(), &'static str> {
467 match v {
468 Some(s) => {
469 *slot = Some(PathBuf::from(s));
470 Ok(())
471 }
472 None => Err("expected a path"),
473 }
474}
475
476fn parse_opt_level(slot: &mut Option<Level>, v: Option<&str>) -> Result<(), &'static str> {
477 match v {
478 Some(s) => {
479 *slot = Some(Level::from_str(s).map_err(|_| "invalid level")?);
480 Ok(())
481 }
482 None => Err("a level"),
483 }
484}
485
486fn parse_opt_include(slot: &mut Vec<String>, v: Option<&str>) -> Result<(), &'static str> {
487 if let Some(include) = v {
488 slot.push(include.to_string());
489 }
490 Ok(())
491}
492
493fn panicking_parser(_s: &str) -> Result<(), String> {
494 panic!("Parsing flux args from cli is not yet supported.");
495}
496
497fn default_pointerwidth(_s: &str) -> Result<PointerWidth, String> {
498 Ok(PointerWidth::default())
499}
500
501fn default_overflowmode(_s: &str) -> Result<OverflowMode, String> {
502 Ok(OverflowMode::default())
503}
504
505fn default_rawderefmode(_s: &str) -> Result<RawDerefMode, String> {
506 Ok(RawDerefMode::default())
507}
508
509fn default_smtsolver(_s: &str) -> Result<SmtSolver, String> {
510 Ok(SmtSolver::default())
511}
512
513fn default_leanmode(_s: &str) -> Result<LeanMode, String> {
514 Ok(LeanMode::default())
515}