pub struct BuiltinHelper {
pub key: &'static str,
pub body_tokens: &'static [&'static str],
pub depends_on: &'static [&'static str],
#[allow(dead_code)]
pub doc: &'static str,
}
pub const BUILTIN_HELPERS: &[BuiltinHelper] = &[
BuiltinHelper {
key: "BranchPath",
body_tokens: &["BranchPath"],
depends_on: &["NumericParse", "BranchPathDatatype"],
doc: "Oracle's structural addressing: `BranchPath.Root`, `.child`, `.parse`. \
Needed whenever any classified-effect function is lifted into proof.",
},
BuiltinHelper {
key: "AverList",
body_tokens: &[
"AverList.",
"ListReverse(",
"ListHead(",
"ListTail(",
"ListTake(",
"ListDrop(",
],
depends_on: &["OptionDatatype"],
doc: "Recursion helpers and structural list utilities (Lean's `AverList.` namespace; \
Dafny's `ListReverse` / `ListHead` / `ListTail` / `ListTake` / `ListDrop`).",
},
BuiltinHelper {
key: "StringHelpers",
body_tokens: &[
"String.charAt",
"String.slice",
"String.chars",
"StringCharAt(",
"StringChars(",
"StringJoin(",
"StringSplit(",
"StringContains(",
"StringStartsWith(",
"StringEndsWith(",
"StringTrim(",
"StringReplace(",
"StringRepeat(",
"StringIndexOf(",
"StringToUpper(",
"StringToLower(",
"StringFromBool(",
"StringByteLength(",
"AverString",
],
depends_on: &["OptionDatatype"],
doc: "Character/slice/intercalate + split/contains/replace/etc. \
Lean: native `String.*`. Dafny: opaque `StringCharAt`, `StringChars`, \
`StringJoin`, `StringSplit`, `StringContains`, etc.",
},
BuiltinHelper {
key: "NumericParse",
body_tokens: &[
"AverDigits.",
"String.fromInt",
"Int.fromString",
"Float.fromString",
"Float.fromInt",
"IntToString(",
"IntFromString(",
"FloatToString(",
"FloatFromString(",
"FloatPi(",
"FloatSqrt(",
"FloatPow(",
"FloatToInt(",
"FloatSin(",
"FloatCos(",
"FloatAtan2(",
],
depends_on: &["ResultDatatype"],
doc: "Decimal parsing/formatting. Lean: full `AverDigits` namespace, `String.fromInt`, \
`Int.fromString`, `Float.fromString`. Dafny: opaque `IntToString` / `IntFromString` \
/ `FloatToString` / `FloatFromString` declarations.",
},
BuiltinHelper {
key: "CharByte",
body_tokens: &[
"Char.toCode",
"Char.fromCode",
"byteToHex",
"AverByte.",
"CharToCode(",
"CharFromCode(",
"ByteToHex(",
"ByteFromHex(",
],
depends_on: &["OptionDatatype", "ResultDatatype"],
doc: "Character-code helpers and hex byte utilities (Lean's `Char.toCode` / `byteToHex` / \
`AverByte`; Dafny's opaque `CharToCode` / `CharFromCode` / `ByteToHex` / `ByteFromHex`).",
},
BuiltinHelper {
key: "AverMeasure",
body_tokens: &["AverMeasure."],
depends_on: &[],
doc: "Decreasing measures used by termination proofs of generic recursion shapes \
(Lean only — Dafny uses native `decreases`).",
},
BuiltinHelper {
key: "AverMap",
body_tokens: &["AverMap.", "MapGet(", "MapEntries(", "MapFromList("],
depends_on: &["OptionDatatype"],
doc: "Map helper namespace. Lean: `AverMap.has_set_self` / `.get_set_self` / etc. \
Dafny: `MapGet` / `MapEntries` / `MapFromList`.",
},
BuiltinHelper {
key: "ProofFuel",
body_tokens: &["averStringPosFuel"],
depends_on: &[],
doc: "Proof-mode fuel measure for string-position recursion (Lean only).",
},
BuiltinHelper {
key: "FloatInstances",
body_tokens: &["Float"],
depends_on: &[],
doc: "`Coe Int Float`, `Float.fromInt`, `Float.unsafeDecEq` / `Float.compDecEq`, \
and the `DecidableEq Float` instance used by `=>`-equality on Float-returning \
functions in proof samples (Lean only).",
},
BuiltinHelper {
key: "ExceptInstances",
body_tokens: &["Except", ".ok", ".error"],
depends_on: &[],
doc: "`DecidableEq (Except ε α)`, the `Except` namespace's `withDefault`, and \
`Option.toExcept`. Needed whenever a function or law mentions a Lean `Except` \
(Aver `Result`) value (Lean only).",
},
BuiltinHelper {
key: "StringHadd",
body_tokens: &["String"],
depends_on: &[],
doc: "`HAdd String String String` instance for string-concat literals like `\"a\" ++ \"b\"`. \
Cheap to ship but unused on pure-Int examples (Lean only).",
},
BuiltinHelper {
key: "ResultDatatype",
body_tokens: &["Result<", "Result.Ok", "Result.Err", "Ok(", "Err("],
depends_on: &[],
doc: "Dafny `datatype Result<T, E>` declaration plus `ResultWithDefault` destructor. \
Lean uses native `Except`; this key is a no-op there.",
},
BuiltinHelper {
key: "OptionDatatype",
body_tokens: &["Option<", "Option.Some", "Option.None", "Some(", "None"],
depends_on: &[],
doc: "Dafny `datatype Option<T>` declaration plus `OptionWithDefault` destructor. \
Lean uses native `Option`; this key is a no-op there.",
},
BuiltinHelper {
key: "OptionToResult",
body_tokens: &["OptionToResult"],
depends_on: &["ResultDatatype", "OptionDatatype"],
doc: "Dafny `OptionToResult` bridge function. Pulled implicitly when both Result and \
Option are in scope and the body explicitly converts between them.",
},
BuiltinHelper {
key: "BranchPathDatatype",
body_tokens: &["BranchPath"],
depends_on: &[],
doc: "Dafny `datatype BranchPath`. Lean's `BranchPath` structure is part of the \
`BranchPath` helper key (the constructors come with it); Dafny separates the \
datatype declaration from the constructor functions so the datatype itself \
can stay even on pure-math files (it doesn't, currently — Dafny no-ops this on pure files).",
},
];
pub fn find(key: &str) -> Option<&'static BuiltinHelper> {
BUILTIN_HELPERS.iter().find(|h| h.key == key)
}
fn any_token_in_body(body: &str, tokens: &[&str]) -> bool {
tokens.iter().any(|t| body.contains(t))
}
pub fn needed_helpers(body: &str, force_all: bool) -> Vec<&'static BuiltinHelper> {
let mut selected: Vec<&'static BuiltinHelper> = Vec::new();
fn include_with_deps(helper: &'static BuiltinHelper, out: &mut Vec<&'static BuiltinHelper>) {
if out.iter().any(|h| h.key == helper.key) {
return;
}
for dep in helper.depends_on {
if let Some(d) = find(dep) {
include_with_deps(d, out);
}
}
out.push(helper);
}
for helper in BUILTIN_HELPERS {
let directly_needed = any_token_in_body(body, helper.body_tokens);
if force_all || directly_needed {
include_with_deps(helper, &mut selected);
}
}
selected
}
#[allow(dead_code)]
pub fn needed_keys(body: &str, force_all: bool) -> Vec<&'static str> {
needed_helpers(body, force_all)
.iter()
.map(|h| h.key)
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_body_needs_no_helpers() {
assert!(needed_helpers("", false).is_empty());
}
#[test]
fn body_with_aver_digits_pulls_numeric_parse_with_result_dep() {
let keys = needed_keys("foo AverDigits.bar baz", false);
assert_eq!(keys, vec!["ResultDatatype", "NumericParse"]);
}
#[test]
fn body_with_string_char_at_pulls_string_helpers() {
let keys = needed_keys("...String.charAt s 0...", false);
assert_eq!(keys, vec!["OptionDatatype", "StringHelpers", "StringHadd"]);
}
#[test]
fn body_with_branch_path_pulls_branch_path_and_its_deps() {
let keys = needed_keys("rollOnce BranchPath.Root rnd", false);
assert_eq!(
keys,
vec![
"ResultDatatype",
"NumericParse",
"BranchPathDatatype",
"BranchPath",
]
);
}
#[test]
fn body_with_multiple_tokens_returns_all_in_emission_order() {
let body = "AverDigits. String.charAt Char.toCode AverList. averStringPosFuel BranchPath";
let keys = needed_keys(body, false);
assert_eq!(
keys,
vec![
"ResultDatatype",
"NumericParse",
"BranchPathDatatype",
"BranchPath",
"OptionDatatype",
"AverList",
"StringHelpers",
"CharByte",
"ProofFuel",
"StringHadd",
]
);
}
#[test]
fn force_all_returns_every_helper() {
let keys = needed_keys("", true);
assert_eq!(
keys,
vec![
"ResultDatatype",
"NumericParse",
"BranchPathDatatype",
"BranchPath",
"OptionDatatype",
"AverList",
"StringHelpers",
"CharByte",
"AverMeasure",
"AverMap",
"ProofFuel",
"FloatInstances",
"ExceptInstances",
"StringHadd",
"OptionToResult",
]
);
}
#[test]
fn pure_int_body_skips_lean_instance_bundles() {
let body = "def absVal (x : Int) : Int := if x < 0 then -x else x";
let keys = needed_keys(body, false);
assert!(!keys.contains(&"FloatInstances"));
assert!(!keys.contains(&"ExceptInstances"));
assert!(!keys.contains(&"StringHadd"));
}
#[test]
fn body_with_float_pulls_float_instances() {
let body = "def f (x : Float) : Float := x + 1.0";
assert!(needed_keys(body, false).contains(&"FloatInstances"));
}
#[test]
fn every_helper_key_lookup_round_trips() {
for h in BUILTIN_HELPERS {
assert_eq!(find(h.key).map(|x| x.key), Some(h.key));
}
}
}