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)]
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)]
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, Clone)]
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, Clone)]
135pub struct FStarOptions<E: Extension> {
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 #[group(flatten)]
169 pub cli_extension: E::FStarOptions,
170}
171
172#[derive_group(Serializers)]
173#[derive(JsonSchema, Subcommand, Debug, Clone)]
174pub enum Backend<E: Extension> {
175 Fstar(FStarOptions<E>),
177 Lean,
179 Coq,
181 Ssprove,
183 Easycrypt,
185 ProVerif(ProVerifOptions),
187 #[clap(hide = true)]
189 Rust,
190 #[clap(hide = true)]
193 GenerateRustEngineNames,
194 Debugger {
196 #[arg(long, short)]
197 interactive: bool,
198 },
199}
200
201impl fmt::Display for Backend<()> {
202 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203 BackendName::from(self).fmt(f)
204 }
205}
206
207#[derive_group(Serializers)]
208#[derive(JsonSchema, Debug, Clone)]
209pub enum DepsKind {
210 Transitive,
211 Shallow,
212 None,
213}
214
215#[derive_group(Serializers)]
216#[derive(JsonSchema, Debug, Clone)]
217pub enum InclusionKind {
218 Included(DepsKind),
220 SignatureOnly,
221 Excluded,
222}
223
224#[derive_group(Serializers)]
225#[derive(JsonSchema, Debug, Clone)]
226pub struct InclusionClause {
227 pub kind: InclusionKind,
228 pub namespace: Namespace,
229}
230
231const PREFIX_INCLUDED_TRANSITIVE: &str = "+";
232const PREFIX_INCLUDED_SHALLOW: &str = "+~";
233const PREFIX_INCLUDED_NONE: &str = "+!";
234const PREFIX_SIGNATURE_ONLY: &str = "+:";
235const PREFIX_EXCLUDED: &str = "-";
236
237impl ToString for InclusionClause {
238 fn to_string(&self) -> String {
239 let kind = match self.kind {
240 InclusionKind::Included(DepsKind::Transitive) => PREFIX_INCLUDED_TRANSITIVE,
241 InclusionKind::Included(DepsKind::Shallow) => PREFIX_INCLUDED_SHALLOW,
242 InclusionKind::Included(DepsKind::None) => PREFIX_INCLUDED_NONE,
243 InclusionKind::SignatureOnly => PREFIX_SIGNATURE_ONLY,
244 InclusionKind::Excluded => PREFIX_EXCLUDED,
245 };
246 format!("{kind}{}", self.namespace.to_string())
247 }
248}
249
250pub fn parse_inclusion_clause(
251 s: &str,
252) -> Result<InclusionClause, Box<dyn std::error::Error + Send + Sync + 'static>> {
253 let s = s.trim();
254 if s.is_empty() {
255 Err("Expected `-` or `+`, got an empty string")?
256 }
257 let (prefix, namespace) = {
258 let f = |&c: &char| matches!(c, '+' | '-' | '~' | '!' | ':');
259 (
260 s.chars().take_while(f).into_iter().collect::<String>(),
261 s.chars().skip_while(f).into_iter().collect::<String>(),
262 )
263 };
264 let kind = match &prefix[..] {
265 PREFIX_INCLUDED_TRANSITIVE => InclusionKind::Included(DepsKind::Transitive),
266 PREFIX_INCLUDED_SHALLOW => InclusionKind::Included(DepsKind::Shallow),
267 PREFIX_INCLUDED_NONE => InclusionKind::Included(DepsKind::None),
268 PREFIX_SIGNATURE_ONLY => InclusionKind::SignatureOnly,
269 PREFIX_EXCLUDED => InclusionKind::Excluded,
270 prefix => Err(format!(
271 "Expected `+`, `+~`, `+!`, `+:` or `-`, got an `{prefix}`"
272 ))?,
273 };
274 Ok(InclusionClause {
275 kind,
276 namespace: namespace.to_string().into(),
277 })
278}
279
280#[derive_group(Serializers)]
281#[derive(JsonSchema, Parser, Debug, Clone)]
282pub struct TranslationOptions {
283 #[arg(
317 value_parser = parse_inclusion_clause,
318 value_delimiter = ' ',
319 )]
320 #[arg(short, allow_hyphen_values(true))]
321 pub include_namespaces: Vec<InclusionClause>,
322}
323
324#[derive_group(Serializers)]
325#[derive(JsonSchema, Parser, Debug, Clone)]
326pub struct BackendOptions<E: Extension> {
327 #[command(subcommand)]
328 pub backend: Backend<E>,
329
330 #[arg(long = "dry-run")]
333 pub dry_run: bool,
334
335 #[arg(short, long, action = clap::ArgAction::Count)]
337 pub verbose: u8,
338
339 #[arg(long)]
342 pub stats: bool,
343
344 #[arg(long)]
347 pub profile: bool,
348
349 #[arg(long)]
354 #[clap(hide = true)]
355 pub prune_haxmeta: Option<String>,
356
357 #[arg(short, long = "debug-engine")]
371 pub debug_engine: Option<DebugEngineMode>,
372
373 #[arg(long)]
381 pub extract_type_aliases: bool,
382
383 #[command(flatten)]
384 pub translation_options: TranslationOptions,
385
386 #[arg(long)]
389 pub output_dir: Option<PathBuf>,
390
391 #[group(flatten)]
392 pub cli_extension: E::BackendOptions,
393}
394
395#[derive_group(Serializers)]
396#[derive(JsonSchema, Subcommand, Debug, Clone)]
397pub enum Command<E: Extension> {
398 #[clap(name = "into")]
403 Backend(BackendOptions<E>),
404
405 JSON {
407 #[arg(
409 short,
410 long = "output-file",
411 default_value = "hax_frontend_export.json"
412 )]
413 output_file: PathOrDash,
414 #[arg(
419 value_enum,
420 short,
421 long = "kind",
422 num_args = 0..=3,
423 default_values_t = [ExportBodyKind::Thir]
424 )]
425 kind: Vec<ExportBodyKind>,
426
427 #[arg(long)]
432 use_ids: bool,
433
434 #[arg(short = 'E', long = "include-extra", default_value = "false")]
436 include_extra: bool,
437 },
438
439 #[clap(hide = true)]
442 Serialize {
443 #[arg(
448 value_enum,
449 short,
450 long = "kind",
451 num_args = 0..=3,
452 default_values_t = [ExportBodyKind::Thir]
453 )]
454 kind: Vec<ExportBodyKind>,
455
456 #[arg(short)]
459 backend: Option<BackendName>,
460 },
461
462 #[command(flatten)]
463 CliExtension(E::Command),
464}
465
466impl<E: Extension> Command<E> {
467 pub fn body_kinds(&self) -> Vec<ExportBodyKind> {
468 match self {
469 Command::JSON { kind, .. } => kind.clone(),
470 Command::Serialize { kind, .. } => kind.clone(),
471 Command::Backend { .. } | Command::CliExtension { .. } => vec![ExportBodyKind::Thir],
472 }
473 }
474 pub fn backend_name(&self) -> Option<BackendName> {
475 match self {
476 Command::Backend(backend_options) => Some((&backend_options.backend).into()),
477 Command::JSON { .. } => None,
478 Command::Serialize { backend, .. } => backend.clone(),
479 Command::CliExtension(_) => None,
480 }
481 }
482}
483
484#[derive_group(Serializers)]
485#[derive(JsonSchema, ValueEnum, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
486pub enum ExportBodyKind {
487 Thir,
488 MirBuilt,
489}
490
491#[derive_group(Serializers)]
492#[derive(JsonSchema, Parser, Debug, Clone)]
493#[command(
494 author,
495 version = crate::HAX_VERSION,
496 long_version = concat!("\nversion=", env!("HAX_VERSION"), "\n", "commit=", env!("HAX_GIT_COMMIT_HASH")),
497 name = "hax",
498 about,
499 long_about = None
500)]
501pub struct ExtensibleOptions<E: Extension> {
502 #[arg(default_values = Vec::<&str>::new(), short='C', allow_hyphen_values=true, num_args=1.., long="cargo-args", value_terminator=";")]
507 pub cargo_flags: Vec<String>,
508
509 #[command(subcommand)]
510 pub command: Command<E>,
511
512 #[arg(long="disable-cargo-cache", action=clap::builder::ArgAction::SetFalse)]
514 pub force_cargo_build: ForceCargoBuild,
515
516 #[arg(long = "deps")]
521 pub deps: bool,
522
523 #[arg(long)]
526 #[clap(hide = true)]
527 pub haxmeta: Option<PathBuf>,
528
529 #[arg(long)]
534 pub no_custom_target_directory: bool,
535
536 #[arg(long, default_value = "human")]
539 pub message_format: MessageFormat,
540
541 #[arg(long, env = "HAX_EXPERIMENTAL_FULL_DEF")]
544 pub experimental_full_def: bool,
545
546 #[group(flatten)]
547 pub extension: E::Options,
548}
549
550pub type Options = ExtensibleOptions<()>;
551
552#[derive_group(Serializers)]
553#[derive(JsonSchema, ValueEnum, Debug, Clone, Copy, Eq, PartialEq)]
554pub enum MessageFormat {
555 Human,
556 Json,
557}
558
559impl<E: Extension> NormalizePaths for Command<E> {
560 fn normalize_paths(&mut self) {
561 use Command::*;
562 match self {
563 JSON { output_file, .. } => output_file.normalize_paths(),
564 _ => (),
565 }
566 }
567}
568
569impl NormalizePaths for Options {
570 fn normalize_paths(&mut self) {
571 self.command.normalize_paths()
572 }
573}
574
575impl From<Options> for hax_frontend_exporter_options::Options {
576 fn from(_opts: Options) -> hax_frontend_exporter_options::Options {
577 hax_frontend_exporter_options::Options {
578 inline_anon_consts: true,
579 bounds_options: hax_frontend_exporter_options::BoundsOptions {
580 resolve_destruct: false,
581 prune_sized: true,
582 },
583 item_ref_use_concrete_impl: false,
584 }
585 }
586}
587
588#[derive_group(Serializers)]
590#[derive(JsonSchema, Debug, Clone)]
591pub struct ExporterOptions {
592 pub deps: bool,
593 pub force_cargo_build: ForceCargoBuild,
594 pub backend: Option<BackendName>,
596 pub body_kinds: Vec<ExportBodyKind>,
597 pub experimental_full_def: bool,
598}
599
600#[derive_group(Serializers)]
601#[derive(JsonSchema, ValueEnum, Debug, Clone, Copy)]
602pub enum BackendName {
603 Fstar,
604 Coq,
605 Ssprove,
606 Easycrypt,
607 ProVerif,
608 Lean,
609 Rust,
610 GenerateRustEngineNames,
611 Debugger,
612}
613
614impl fmt::Display for BackendName {
615 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
616 let name = match self {
617 BackendName::Fstar => "fstar",
618 BackendName::Coq => "coq",
619 BackendName::Ssprove => "ssprove",
620 BackendName::Easycrypt => "easycrypt",
621 BackendName::ProVerif => "proverif",
622 BackendName::Lean => "lean",
623 BackendName::Rust => "rust",
624 BackendName::GenerateRustEngineNames => "generate_rust_engine_names",
625 BackendName::Debugger => "debugger",
626 };
627 write!(f, "{name}")
628 }
629}
630
631impl From<&Options> for ExporterOptions {
632 fn from(options: &Options) -> Self {
633 ExporterOptions {
634 deps: options.deps,
635 force_cargo_build: options.force_cargo_build.clone(),
636 backend: options.command.backend_name(),
637 body_kinds: options.command.body_kinds(),
638 experimental_full_def: options.experimental_full_def,
639 }
640 }
641}
642
643impl<E: Extension> From<&Backend<E>> for BackendName {
644 fn from(backend: &Backend<E>) -> Self {
645 match backend {
646 Backend::Fstar { .. } => BackendName::Fstar,
647 Backend::Coq { .. } => BackendName::Coq,
648 Backend::Ssprove { .. } => BackendName::Ssprove,
649 Backend::Easycrypt { .. } => BackendName::Easycrypt,
650 Backend::ProVerif { .. } => BackendName::ProVerif,
651 Backend::Lean { .. } => BackendName::Lean,
652 Backend::Rust { .. } => BackendName::Rust,
653 Backend::GenerateRustEngineNames { .. } => BackendName::GenerateRustEngineNames,
654 Backend::Debugger { .. } => BackendName::Debugger,
655 }
656 }
657}
658
659pub const ENV_VAR_OPTIONS_FRONTEND: &str = "DRIVER_HAX_FRONTEND_OPTS";
660pub const ENV_VAR_OPTIONS_FULL: &str = "DRIVER_HAX_FRONTEND_FULL_OPTS";