1use crate::prelude::*;
2
3use clap::{Parser, Subcommand, ValueEnum};
4use std::fmt;
5
6pub use hax_frontend_exporter_options::*;
7pub mod extension;
8use extension::Extension;
9
10#[derive_group(Serializers)]
11#[derive(JsonSchema, Debug, Clone, Eq, PartialEq)]
12pub enum DebugEngineMode {
13 File(PathOrDash),
14 Interactive,
15}
16
17impl std::convert::From<&str> for DebugEngineMode {
18 fn from(s: &str) -> Self {
19 match s {
20 "i" | "interactively" => DebugEngineMode::Interactive,
21 s => DebugEngineMode::File(s.strip_prefix("file:").unwrap_or(s).into()),
22 }
23 }
24}
25
26#[derive_group(Serializers)]
27#[derive(JsonSchema, Debug, Clone, Default)]
28pub struct ForceCargoBuild {
29 pub data: u64,
30}
31
32impl std::convert::From<&str> for ForceCargoBuild {
33 fn from(s: &str) -> Self {
34 use std::time::{SystemTime, UNIX_EPOCH};
35 if s == "false" {
36 let data = SystemTime::now()
37 .duration_since(UNIX_EPOCH)
38 .map(|r| r.as_millis())
39 .unwrap_or(0);
40 ForceCargoBuild { data: data as u64 }
41 } else {
42 ForceCargoBuild::default()
43 }
44 }
45}
46
47#[derive_group(Serializers)]
48#[derive(Debug, Clone, JsonSchema, Eq, PartialEq)]
49pub enum PathOrDash {
50 Dash,
51 Path(PathBuf),
52}
53
54impl std::convert::From<&str> for PathOrDash {
55 fn from(s: &str) -> Self {
56 match s {
57 "-" => PathOrDash::Dash,
58 _ => PathOrDash::Path(PathBuf::from(s)),
59 }
60 }
61}
62
63impl PathOrDash {
64 pub fn open_or_stdout(&self) -> Box<dyn std::io::Write> {
65 use std::io::BufWriter;
66 match self {
67 PathOrDash::Dash => Box::new(BufWriter::new(std::io::stdout())),
68 PathOrDash::Path(path) => {
69 Box::new(BufWriter::new(std::fs::File::create(&path).unwrap()))
70 }
71 }
72 }
73 pub fn map_path<F: FnOnce(&Path) -> PathBuf>(&self, f: F) -> Self {
74 match self {
75 PathOrDash::Path(path) => PathOrDash::Path(f(path)),
76 PathOrDash::Dash => PathOrDash::Dash,
77 }
78 }
79}
80
81fn absolute_path(path: impl AsRef<std::path::Path>) -> std::io::Result<std::path::PathBuf> {
82 use path_clean::PathClean;
83 let path = path.as_ref();
84
85 let absolute_path = if path.is_absolute() {
86 path.to_path_buf()
87 } else {
88 std::env::current_dir()?.join(path)
89 }
90 .clean();
91
92 Ok(absolute_path)
93}
94
95pub trait NormalizePaths {
96 fn normalize_paths(&mut self);
97}
98
99impl NormalizePaths for PathBuf {
100 fn normalize_paths(&mut self) {
101 *self = absolute_path(&self).unwrap();
102 }
103}
104impl NormalizePaths for PathOrDash {
105 fn normalize_paths(&mut self) {
106 match self {
107 PathOrDash::Path(p) => p.normalize_paths(),
108 PathOrDash::Dash => (),
109 }
110 }
111}
112
113#[derive_group(Serializers)]
114#[derive(JsonSchema, Parser, Debug, Hash, Clone, Eq, PartialEq)]
115pub struct ProVerifOptions {
116 #[arg(
125 long,
126 value_parser = parse_inclusion_clause,
127 value_delimiter = ' ',
128 allow_hyphen_values(true)
129 )]
130 pub assume_items: Vec<InclusionClause>,
131}
132
133#[derive_group(Serializers)]
134#[derive(JsonSchema, Parser, Debug, Hash, Clone, Eq, PartialEq)]
135pub struct FStarOptions {
136 #[arg(long, default_value = "15")]
138 pub z3rlimit: u32,
139 #[arg(long, default_value = "0")]
141 pub fuel: u32,
142 #[arg(long, default_value = "1")]
144 pub ifuel: u32,
145 #[arg(
158 long,
159 value_parser = parse_inclusion_clause,
160 value_delimiter = ' ',
161 allow_hyphen_values(true)
162 )]
163 pub interfaces: Vec<InclusionClause>,
164
165 #[arg(long, default_value = "100", env = "HAX_FSTAR_LINE_WIDTH")]
166 pub line_width: u16,
167}
168
169#[derive_group(Serializers)]
170#[derive(JsonSchema, Parser, Debug, Clone, Hash, Eq, PartialEq)]
171#[command(after_help = concat!("\
172TOOLS:
173 This backend runs `charon`, then `aeneas`, and scaffolds a Lean proof project.
174 Each tool is pinned; the pinned version is checked against the resolved binary
175 at runtime (a mismatch is a non-fatal warning).
176
177 charon expected version ", env!("HAX_CHARON_PIN_VERSION"), "
178 located at $HAX_CHARON_BINARY (absolute path) if set, else `charon` found in PATH
179 aeneas expected commit ", env!("HAX_AENEAS_PIN_VERSION"), "
180 located at $HAX_AENEAS_BINARY (absolute path) if set, else `aeneas` found in PATH
181 lean expected toolchain ", env!("HAX_LEAN_PIN_TOOLCHAIN"), "
182 used by the generated proof project (written to its `lean-toolchain`)
183
184 Install charon/aeneas with `install-aeneas.sh`.
185
186INVOCATION:
187 The tools are run with some fixed flags (to which any --charon-args/--aeneas-args
188 are appended). Pass `-v` (`cargo hax into -v lean`) to print the exact command
189 before each tool runs.
190
191 Overriding a flag that controls where output is written (aeneas's -backend,
192 -dest, -subdir, or -split-files, or charon's --dest-file) may break the extraction
193 or the generated proof project.
194
195ENVIRONMENT VARIABLES:
196 HAX_CHARON_BINARY Path to the `charon` binary to use. Defaults to `charon` found in PATH.
197 HAX_AENEAS_BINARY Path to the `aeneas` binary to use. Defaults to `aeneas` found in PATH."))]
198pub struct LeanOptions {
199 #[arg(long)]
204 pub lakefile: bool,
205
206 #[arg(long)]
210 pub charon_args: Option<String>,
211
212 #[arg(long)]
215 pub aeneas_args: Option<String>,
216}
217
218#[derive_group(Serializers)]
219#[derive(JsonSchema, Subcommand, Debug, Clone, Hash, Eq, PartialEq)]
220pub enum Backend {
221 Fstar(FStarOptions),
223 LegacyLean,
225 Lean(LeanOptions),
227 Coq,
229 Ssprove,
231 Easycrypt,
233 #[clap(alias("proverif"))]
235 ProVerif(ProVerifOptions),
236 #[clap(hide = true)]
238 Rust,
239 #[clap(hide = true)]
242 GenerateRustEngineNames,
243 Debugger {
245 #[arg(long, short)]
246 interactive: bool,
247 },
248}
249
250impl fmt::Display for Backend {
251 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252 BackendName::from(self).fmt(f)
253 }
254}
255
256#[derive_group(Serializers)]
257#[derive(JsonSchema, Debug, Hash, Clone, Eq, PartialEq)]
258pub enum DepsKind {
259 Transitive,
260 Shallow,
261 None,
262}
263
264#[derive_group(Serializers)]
265#[derive(JsonSchema, Debug, Hash, Clone, Eq, PartialEq)]
266pub enum InclusionKind {
267 Included(DepsKind),
269 SignatureOnly,
270 Excluded,
271}
272
273#[derive_group(Serializers)]
274#[derive(JsonSchema, Debug, Hash, Clone, Eq, PartialEq)]
275pub struct InclusionClause {
276 pub kind: InclusionKind,
277 pub namespace: Namespace,
278}
279
280const PREFIX_INCLUDED_TRANSITIVE: &str = "+";
281const PREFIX_INCLUDED_SHALLOW: &str = "+~";
282const PREFIX_INCLUDED_NONE: &str = "+!";
283const PREFIX_SIGNATURE_ONLY: &str = "+:";
284const PREFIX_EXCLUDED: &str = "-";
285
286impl ToString for InclusionClause {
287 fn to_string(&self) -> String {
288 let kind = match self.kind {
289 InclusionKind::Included(DepsKind::Transitive) => PREFIX_INCLUDED_TRANSITIVE,
290 InclusionKind::Included(DepsKind::Shallow) => PREFIX_INCLUDED_SHALLOW,
291 InclusionKind::Included(DepsKind::None) => PREFIX_INCLUDED_NONE,
292 InclusionKind::SignatureOnly => PREFIX_SIGNATURE_ONLY,
293 InclusionKind::Excluded => PREFIX_EXCLUDED,
294 };
295 format!("{kind}{}", self.namespace.to_string())
296 }
297}
298
299pub fn parse_inclusion_clause(
300 s: &str,
301) -> Result<InclusionClause, Box<dyn std::error::Error + Send + Sync + 'static>> {
302 let s = s.trim();
303 if s.is_empty() {
304 Err("Expected `-` or `+`, got an empty string")?
305 }
306 let (prefix, namespace) = {
307 let f = |&c: &char| matches!(c, '+' | '-' | '~' | '!' | ':');
308 (
309 s.chars().take_while(f).into_iter().collect::<String>(),
310 s.chars().skip_while(f).into_iter().collect::<String>(),
311 )
312 };
313 let kind = match &prefix[..] {
314 PREFIX_INCLUDED_TRANSITIVE => InclusionKind::Included(DepsKind::Transitive),
315 PREFIX_INCLUDED_SHALLOW => InclusionKind::Included(DepsKind::Shallow),
316 PREFIX_INCLUDED_NONE => InclusionKind::Included(DepsKind::None),
317 PREFIX_SIGNATURE_ONLY => InclusionKind::SignatureOnly,
318 PREFIX_EXCLUDED => InclusionKind::Excluded,
319 prefix => Err(format!(
320 "Expected `+`, `+~`, `+!`, `+:` or `-`, got an `{prefix}`"
321 ))?,
322 };
323 Ok(InclusionClause {
324 kind,
325 namespace: namespace.to_string().into(),
326 })
327}
328
329#[derive_group(Serializers)]
330#[derive(JsonSchema, Parser, Debug, Clone, Eq, PartialEq)]
331pub struct TranslationOptions {
332 #[arg(
366 value_parser = parse_inclusion_clause,
367 value_delimiter = ' ',
368 )]
369 #[arg(short, allow_hyphen_values(true))]
370 pub include_namespaces: Vec<InclusionClause>,
371}
372
373#[derive_group(Serializers)]
374#[derive(JsonSchema, Parser, Debug, Clone, Eq, PartialEq)]
375pub struct BackendOptions<E: Extension> {
376 #[command(subcommand)]
377 pub backend: Backend,
378
379 #[arg(long = "dry-run")]
382 pub dry_run: bool,
383
384 #[arg(short, long, action = clap::ArgAction::Count)]
386 pub verbose: u8,
387
388 #[arg(long)]
391 pub stats: bool,
392
393 #[arg(long)]
396 pub profile: bool,
397
398 #[arg(long)]
403 #[clap(hide = true)]
404 pub prune_haxmeta: Option<String>,
405
406 #[arg(short, long = "debug-engine")]
420 pub debug_engine: Option<DebugEngineMode>,
421
422 #[arg(long)]
430 pub extract_type_aliases: bool,
431
432 #[command(flatten)]
433 pub translation_options: TranslationOptions,
434
435 #[arg(long)]
438 pub output_dir: Option<PathBuf>,
439
440 #[group(flatten)]
441 pub cli_extension: E::BackendOptions,
442}
443
444#[derive_group(Serializers)]
445#[derive(JsonSchema, Subcommand, Debug, Clone, Eq, PartialEq)]
446pub enum Command<E: Extension> {
447 #[clap(name = "into")]
452 Backend(BackendOptions<E>),
453
454 JSON {
456 #[arg(
458 short,
459 long = "output-file",
460 default_value = "hax_frontend_export.json"
461 )]
462 output_file: PathOrDash,
463 #[arg(
468 value_enum,
469 short,
470 long = "kind",
471 num_args = 0..=3,
472 default_values_t = [ExportBodyKind::Thir]
473 )]
474 kind: Vec<ExportBodyKind>,
475
476 #[arg(long)]
481 use_ids: bool,
482
483 #[arg(short = 'E', long = "include-extra", default_value = "false")]
485 include_extra: bool,
486 },
487
488 #[clap(hide = true)]
491 Serialize {
492 #[arg(
497 value_enum,
498 short,
499 long = "kind",
500 num_args = 0..=3,
501 default_values_t = [ExportBodyKind::Thir]
502 )]
503 kind: Vec<ExportBodyKind>,
504
505 #[arg(short)]
508 backend: Option<BackendName>,
509 },
510
511 #[command(flatten)]
512 CliExtension(E::Command),
513}
514
515impl<E: Extension> Command<E> {
516 pub fn body_kinds(&self) -> Vec<ExportBodyKind> {
517 match self {
518 Command::JSON { kind, .. } => kind.clone(),
519 Command::Serialize { kind, .. } => kind.clone(),
520 Command::Backend { .. } | Command::CliExtension { .. } => vec![ExportBodyKind::Thir],
521 }
522 }
523 pub fn backend_name(&self) -> Option<BackendName> {
524 match self {
525 Command::Backend(backend_options) => Some((&backend_options.backend).into()),
526 Command::JSON { .. } => None,
527 Command::Serialize { backend, .. } => backend.clone(),
528 Command::CliExtension(_) => None,
529 }
530 }
531}
532
533#[derive_group(Serializers)]
534#[derive(JsonSchema, ValueEnum, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
535pub enum ExportBodyKind {
536 Thir,
537 MirBuilt,
538}
539
540#[derive_group(Serializers)]
541#[derive(JsonSchema, Parser, Debug, Clone)]
542#[command(
543 author,
544 version = crate::HAX_VERSION,
545 long_version = concat!("\nversion=", env!("HAX_VERSION"), "\n", "commit=", env!("HAX_GIT_COMMIT_HASH")),
546 name = "hax",
547 about,
548 long_about = None
549)]
550pub struct ExtensibleOptions<E: Extension> {
551 #[arg(default_values = Vec::<&str>::new(), short='C', allow_hyphen_values=true, num_args=1.., long="cargo-args", value_terminator=";")]
556 pub cargo_flags: Vec<String>,
557
558 #[command(subcommand)]
559 pub command: Command<E>,
560
561 #[arg(long="disable-cargo-cache", action=clap::builder::ArgAction::SetFalse)]
563 pub force_cargo_build: ForceCargoBuild,
564
565 #[arg(long = "deps")]
570 pub deps: bool,
571
572 #[arg(long)]
575 #[clap(hide = true)]
576 pub haxmeta: Option<PathBuf>,
577
578 #[arg(long)]
583 pub no_custom_target_directory: bool,
584
585 #[arg(long, default_value = "human")]
588 pub message_format: MessageFormat,
589
590 #[arg(long, env = "HAX_EXPERIMENTAL_FULL_DEF")]
593 pub experimental_full_def: bool,
594
595 #[group(flatten)]
596 pub extension: E::Options,
597}
598
599pub type Options = ExtensibleOptions<()>;
600
601#[derive_group(Serializers)]
602#[derive(JsonSchema, ValueEnum, Debug, Clone, Copy, Eq, PartialEq)]
603pub enum MessageFormat {
604 Human,
605 Json,
606}
607
608impl<E: Extension> NormalizePaths for Command<E> {
609 fn normalize_paths(&mut self) {
610 use Command::*;
611 match self {
612 JSON { output_file, .. } => output_file.normalize_paths(),
613 _ => (),
614 }
615 }
616}
617
618impl NormalizePaths for Options {
619 fn normalize_paths(&mut self) {
620 self.command.normalize_paths()
621 }
622}
623
624impl From<Options> for hax_frontend_exporter_options::Options {
625 fn from(_opts: Options) -> hax_frontend_exporter_options::Options {
626 hax_frontend_exporter_options::Options {
627 inline_anon_consts: true,
628 bounds_options: hax_frontend_exporter_options::BoundsOptions {
629 resolve_destruct: false,
630 prune_sized: true,
631 },
632 item_ref_use_concrete_impl: false,
633 }
634 }
635}
636
637#[derive_group(Serializers)]
639#[derive(JsonSchema, Debug, Clone)]
640pub struct ExporterOptions {
641 pub deps: bool,
642 pub force_cargo_build: ForceCargoBuild,
643 pub backend: Option<BackendName>,
645 pub body_kinds: Vec<ExportBodyKind>,
646 pub experimental_full_def: bool,
647}
648
649#[derive_group(Serializers)]
650#[derive(JsonSchema, ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Hash)]
651pub enum BackendName {
652 Fstar,
653 Coq,
654 Ssprove,
655 Easycrypt,
656 #[clap(alias("proverif"))]
657 ProVerif,
658 LegacyLean,
659 Lean,
660 Rust,
661 GenerateRustEngineNames,
662 Debugger,
663}
664
665impl BackendName {
666 pub fn iter() -> impl Iterator<Item = Self> {
667 [
668 Self::Fstar,
669 Self::Coq,
670 Self::Ssprove,
671 Self::Easycrypt,
672 Self::ProVerif,
673 Self::LegacyLean,
674 Self::Lean,
675 Self::Rust,
676 Self::GenerateRustEngineNames,
677 ]
678 .into_iter()
679 }
680}
681
682impl fmt::Display for BackendName {
683 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
684 let name = match self {
685 BackendName::Fstar => "fstar",
686 BackendName::Coq => "coq",
687 BackendName::Ssprove => "ssprove",
688 BackendName::Easycrypt => "easycrypt",
689 BackendName::ProVerif => "proverif",
690 BackendName::LegacyLean => "legacy-lean",
691 BackendName::Lean => "lean",
692 BackendName::Rust => "rust",
693 BackendName::GenerateRustEngineNames => "generate_rust_engine_names",
694 BackendName::Debugger => "debugger",
695 };
696 write!(f, "{name}")
697 }
698}
699
700impl From<&Options> for ExporterOptions {
701 fn from(options: &Options) -> Self {
702 ExporterOptions {
703 deps: options.deps,
704 force_cargo_build: options.force_cargo_build.clone(),
705 backend: options.command.backend_name(),
706 body_kinds: options.command.body_kinds(),
707 experimental_full_def: options.experimental_full_def,
708 }
709 }
710}
711
712impl From<&Backend> for BackendName {
713 fn from(backend: &Backend) -> Self {
714 match backend {
715 Backend::Fstar { .. } => BackendName::Fstar,
716 Backend::Coq { .. } => BackendName::Coq,
717 Backend::Ssprove { .. } => BackendName::Ssprove,
718 Backend::Easycrypt { .. } => BackendName::Easycrypt,
719 Backend::ProVerif { .. } => BackendName::ProVerif,
720 Backend::LegacyLean { .. } => BackendName::LegacyLean,
721 Backend::Lean { .. } => BackendName::Lean,
722 Backend::Rust { .. } => BackendName::Rust,
723 Backend::GenerateRustEngineNames { .. } => BackendName::GenerateRustEngineNames,
724 Backend::Debugger { .. } => BackendName::Debugger,
725 }
726 }
727}
728
729pub const ENV_VAR_OPTIONS_FRONTEND: &str = "DRIVER_HAX_FRONTEND_OPTS";
730pub const ENV_VAR_OPTIONS_FULL: &str = "DRIVER_HAX_FRONTEND_FULL_OPTS";