aver/codegen/dafny/
mod.rs1mod expr;
10mod toplevel;
11
12use crate::ast::{TopLevel, VerifyKind};
13use crate::codegen::{CodegenContext, ProjectOutput};
14
15fn body_uses_error_prop(body: &std::rc::Rc<crate::ast::FnBody>) -> bool {
18 match body.as_ref() {
19 crate::ast::FnBody::Block(stmts) => stmts.iter().any(|s| match s {
20 crate::ast::Stmt::Binding(_, _, expr) => expr_uses_error_prop(expr),
21 crate::ast::Stmt::Expr(expr) => expr_uses_error_prop(expr),
22 }),
23 }
24}
25
26fn expr_uses_error_prop(expr: &crate::ast::Expr) -> bool {
27 use crate::ast::Expr;
28 match expr {
29 Expr::ErrorProp(_) => true,
30 Expr::FnCall(f, args) => expr_uses_error_prop(f) || args.iter().any(expr_uses_error_prop),
31 Expr::BinOp(_, l, r) => expr_uses_error_prop(l) || expr_uses_error_prop(r),
32 Expr::Match { subject, arms, .. } => {
33 expr_uses_error_prop(subject) || arms.iter().any(|a| expr_uses_error_prop(&a.body))
34 }
35 Expr::Constructor(_, Some(arg)) => expr_uses_error_prop(arg),
36 Expr::List(elems) | Expr::Tuple(elems) => elems.iter().any(expr_uses_error_prop),
37 Expr::RecordCreate { fields, .. } => fields.iter().any(|(_, e)| expr_uses_error_prop(e)),
38 Expr::RecordUpdate { base, updates, .. } => {
39 expr_uses_error_prop(base) || updates.iter().any(|(_, e)| expr_uses_error_prop(e))
40 }
41 Expr::InterpolatedStr(parts) => parts.iter().any(|p| match p {
42 crate::ast::StrPart::Parsed(e) => expr_uses_error_prop(e),
43 _ => false,
44 }),
45 Expr::Attr(obj, _) => expr_uses_error_prop(obj),
46 Expr::TailCall(inner) => inner.1.iter().any(expr_uses_error_prop),
47 _ => false,
48 }
49}
50
51pub fn transpile(ctx: &CodegenContext) -> ProjectOutput {
53 let mut sections: Vec<String> = Vec::new();
54
55 sections.push("// Generated by the Aver → Dafny transpiler".to_string());
56 sections.push("// Pure core logic only (effectful functions are omitted)\n".to_string());
57
58 sections.push(DAFNY_PRELUDE.to_string());
60
61 for module in &ctx.modules {
63 for td in &module.type_defs {
64 if let Some(code) = toplevel::emit_type_def(td) {
65 sections.push(code);
66 }
67 }
68 }
69
70 for td in &ctx.type_defs {
72 if let Some(code) = toplevel::emit_type_def(td) {
73 sections.push(code);
74 }
75 }
76
77 for module in &ctx.modules {
79 for fd in &module.fn_defs {
80 if fd.effects.is_empty() && !body_uses_error_prop(&fd.body) {
81 sections.push(toplevel::emit_fn_def(fd, ctx));
82 }
83 }
84 }
85
86 for item in &ctx.items {
88 if let TopLevel::FnDef(fd) = item
89 && fd.effects.is_empty()
90 && fd.name != "main"
91 && !body_uses_error_prop(&fd.body)
92 {
93 sections.push(toplevel::emit_fn_def(fd, ctx));
94 }
95 }
96
97 let mut law_counter: std::collections::HashMap<String, usize> =
99 std::collections::HashMap::new();
100 for item in &ctx.items {
101 if let TopLevel::Verify(vb) = item
102 && let VerifyKind::Law(law) = &vb.kind
103 {
104 let count = law_counter.entry(vb.fn_name.clone()).or_insert(0);
105 *count += 1;
106 let suffix = if *count > 1 {
107 format!("_{}", count)
108 } else {
109 String::new()
110 };
111
112 if !vb.cases.is_empty()
114 && let Some(code) = toplevel::emit_law_samples(vb, law, ctx, &suffix)
115 {
116 sections.push(code);
117 }
118
119 sections.push(toplevel::emit_verify_law(vb, law, ctx));
121 }
122 }
123
124 let content = sections.join("\n");
125 let file_name = format!("{}.dfy", ctx.project_name);
126
127 ProjectOutput {
128 files: vec![(file_name, content)],
129 }
130}
131
132const DAFNY_PRELUDE: &str = r#"// --- Prelude: standard types and helpers ---
133
134datatype Result<T, E> = Ok(value: T) | Err(error: E)
135
136datatype Option<T> = None | Some(value: T)
137
138function ResultWithDefault<T, E>(r: Result<T, E>, d: T): T {
139 match r
140 case Ok(v) => v
141 case Err(_) => d
142}
143
144function OptionWithDefault<T>(o: Option<T>, d: T): T {
145 match o
146 case Some(v) => v
147 case None => d
148}
149
150function OptionToResult<T, E>(o: Option<T>, err: E): Result<T, E> {
151 match o
152 case Some(v) => Result.Ok(v)
153 case None => Result.Err(err)
154}
155
156function ListReverse<T>(xs: seq<T>): seq<T>
157 decreases |xs|
158{
159 if |xs| == 0 then []
160 else ListReverse(xs[1..]) + [xs[0]]
161}
162
163function ListHead<T>(xs: seq<T>): Option<T> {
164 if |xs| == 0 then None
165 else Some(xs[0])
166}
167
168function ListTail<T>(xs: seq<T>): seq<T> {
169 if |xs| == 0 then []
170 else xs[1..]
171}
172
173function MapGet<K, V>(m: map<K, V>, k: K): Option<V> {
174 if k in m then Some(m[k])
175 else None
176}
177
178// --- String/Char helpers (opaque stubs for verification) ---
179
180function IntToString(n: int): string
181function IntFromString(s: string): Result<int, string>
182function FloatToString(r: real): string
183function FloatFromString(s: string): Result<real, string>
184function StringCharAt(s: string, i: int): Option<string> {
185 if 0 <= i < |s| then Option.Some([s[i]]) else Option.None
186}
187function StringChars(s: string): seq<string> {
188 seq(|s|, (i: int) requires 0 <= i < |s| => [s[i]])
189}
190function StringJoin(sep: string, parts: seq<string>): string
191 decreases |parts|
192{
193 if |parts| == 0 then ""
194 else if |parts| == 1 then parts[0]
195 else parts[0] + sep + StringJoin(sep, parts[1..])
196}
197function CharToCode(c: string): int
198function CharFromCode(n: int): Option<string>
199function MapEntries<K, V>(m: map<K, V>): seq<(K, V)>
200function MapFromList<K, V>(entries: seq<(K, V)>): map<K, V>
201 decreases |entries|
202{
203 if |entries| == 0 then map[]
204 else MapFromList(entries[..|entries|-1])[entries[|entries|-1].0 := entries[|entries|-1].1]
205}
206function ByteToHex(b: int): Result<string, string>
207function ByteFromHex(s: string): Result<int, string>
208function ToString<T>(v: T): string
209"#;