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::sync::Arc<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::Spanned<crate::ast::Expr>) -> bool {
27 use crate::ast::Expr;
28 match &expr.node {
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 ListTake<T>(xs: seq<T>, n: int): seq<T> {
174 if n <= 0 then []
175 else if n >= |xs| then xs
176 else xs[..n]
177}
178
179function ListDrop<T>(xs: seq<T>, n: int): seq<T> {
180 if n <= 0 then xs
181 else if n >= |xs| then []
182 else xs[n..]
183}
184
185function MapGet<K, V>(m: map<K, V>, k: K): Option<V> {
186 if k in m then Some(m[k])
187 else None
188}
189
190// --- String/Char helpers (opaque stubs for verification) ---
191
192function IntToString(n: int): string
193function IntFromString(s: string): Result<int, string>
194function FloatToString(r: real): string
195function FloatFromString(s: string): Result<real, string>
196function StringCharAt(s: string, i: int): Option<string> {
197 if 0 <= i < |s| then Option.Some([s[i]]) else Option.None
198}
199function StringChars(s: string): seq<string> {
200 seq(|s|, (i: int) requires 0 <= i < |s| => [s[i]])
201}
202function StringJoin(sep: string, parts: seq<string>): string
203 decreases |parts|
204{
205 if |parts| == 0 then ""
206 else if |parts| == 1 then parts[0]
207 else parts[0] + sep + StringJoin(sep, parts[1..])
208}
209function CharToCode(c: string): int
210function CharFromCode(n: int): Option<string>
211function MapEntries<K, V>(m: map<K, V>): seq<(K, V)>
212function MapFromList<K, V>(entries: seq<(K, V)>): map<K, V>
213 decreases |entries|
214{
215 if |entries| == 0 then map[]
216 else MapFromList(entries[..|entries|-1])[entries[|entries|-1].0 := entries[|entries|-1].1]
217}
218function ByteToHex(b: int): Result<string, string>
219function ByteFromHex(s: string): Result<int, string>
220function ToString<T>(v: T): string
221"#;