use super::builtins;
use super::pattern::emit_pattern;
use super::shared::to_lower_first;
use crate::ast::{BinOp, Literal, Spanned};
use crate::codegen::CodegenContext;
use crate::codegen::common::{is_user_type, resolve_module_call};
use crate::ir::hir::{
BuiltinCtor, ResolvedCallee, ResolvedCtor, ResolvedExpr, ResolvedMatchArm, ResolvedPattern,
ResolvedStmt, ResolvedStrPart,
};
pub fn emit_expr(expr: &Spanned<ResolvedExpr>, ctx: &CodegenContext) -> String {
match &expr.node {
ResolvedExpr::Literal(lit) => emit_literal(lit),
ResolvedExpr::Ident(name) | ResolvedExpr::Resolved { name, .. }
if name == crate::codegen::recursion::OMEGA_PROOF_SENTINEL =>
{
"(by omega)".to_string()
}
ResolvedExpr::Ident(name) | ResolvedExpr::Resolved { name, .. } => aver_name_to_lean(name),
ResolvedExpr::Attr(obj, field) => {
if let Some(ty) = obj.ty()
&& let Some(decl) = crate::codegen::common::find_refined_type_for_named(ctx, ty)
&& decl.carrier_type == "Int"
&& field == &decl.carrier_field
{
let obj_str = emit_expr(obj, ctx);
let needs_parens = !matches!(
&obj.node,
ResolvedExpr::Ident(_) | ResolvedExpr::Resolved { .. }
);
return if needs_parens {
format!("({obj_str}).val")
} else {
format!("{obj_str}.val")
};
}
if let ResolvedExpr::Ident(type_name) = &obj.node {
if type_name == "Option" && field == "None" {
return "none".to_string();
}
if type_name == "BranchPath" && field == "Root" {
return "BranchPath.Root".to_string();
}
if is_user_type(type_name, ctx) {
return format!("{}.{}", type_name, to_lower_first(field));
}
}
if let Some(full_dotted) = crate::ir::hir::resolved_to_dotted(&expr.node)
&& let Some((prefix, bare)) = resolve_module_call(&full_dotted, ctx)
{
if let Some(dot_pos) = bare.find('.') {
let type_name = &bare[..dot_pos];
let variant = &bare[dot_pos + 1..];
if is_user_type(type_name, ctx) {
return format!("{}.{}", type_name, to_lower_first(variant));
}
}
let bare_lean = aver_name_to_lean(bare);
if !ctx.modules.is_empty() {
return format!("{}.{}", prefix, bare_lean);
}
return bare_lean;
}
let obj_str = emit_expr(obj, ctx);
let needs_parens =
!matches!(&obj.node, ResolvedExpr::Ident(_) | ResolvedExpr::Attr(_, _));
if needs_parens {
format!("({}).{}", obj_str, aver_name_to_lean(field))
} else {
format!("{}.{}", obj_str, aver_name_to_lean(field))
}
}
ResolvedExpr::Call(callee, args) => emit_fn_call(callee, args, ctx),
ResolvedExpr::Neg(inner) => format!("(-{})", emit_expr(inner, ctx)),
ResolvedExpr::BinOp(op, left, right) => {
let l = emit_expr(left, ctx);
let r = emit_expr(right, ctx);
let op_str = match op {
BinOp::Add => "+",
BinOp::Sub => "-",
BinOp::Mul => "*",
BinOp::Div => "/",
BinOp::Eq => "==",
BinOp::Neq => "!=",
BinOp::Lt => "<",
BinOp::Gt => ">",
BinOp::Lte => "<=",
BinOp::Gte => ">=",
};
format!("({} {} {})", l, op_str, r)
}
ResolvedExpr::Match { subject, arms } => emit_match(subject, arms, expr.line, ctx),
ResolvedExpr::Ctor(ctor, args) => emit_constructor(ctor, args, ctx),
ResolvedExpr::ErrorProp(inner) => {
let inner_str = emit_expr(inner, ctx);
format!("(({}).withDefault default)", inner_str)
}
ResolvedExpr::InterpolatedStr(parts) => emit_interpolated_str(parts, ctx),
ResolvedExpr::List(elements) => {
if elements.is_empty() {
"[]".to_string()
} else {
let parts: Vec<String> = elements.iter().map(|e| emit_expr(e, ctx)).collect();
format!("[{}]", parts.join(", "))
}
}
ResolvedExpr::Tuple(items) | ResolvedExpr::IndependentProduct(items, _) => {
let parts: Vec<String> = items.iter().map(|e| emit_expr(e, ctx)).collect();
format!("({})", parts.join(", "))
}
ResolvedExpr::MapLiteral(entries) => {
if entries.is_empty() {
"[]".to_string()
} else if entries
.iter()
.all(|(_, v)| crate::codegen::common::is_unit_expr_resolved(&v.node))
{
let parts: Vec<String> = entries.iter().map(|(k, _)| emit_expr(k, ctx)).collect();
format!("AverSet.ofList [{}]", parts.join(", "))
} else {
let parts: Vec<String> = entries
.iter()
.map(|(k, v)| format!("({}, {})", emit_expr(k, ctx), emit_expr(v, ctx)))
.collect();
format!("[{}]", parts.join(", "))
}
}
ResolvedExpr::RecordCreate {
type_name, fields, ..
} => {
if let Some(decl) = crate::codegen::common::find_refined_type(ctx, type_name)
&& decl.carrier_type == "Int"
&& fields.len() == 1
{
let (_, value_expr) = &fields[0];
let value_str = emit_expr(value_expr, ctx);
return format!(
"⟨{value_str}, by first | omega | decide | (simp_all; omega) | assumption⟩"
);
}
let parts: Vec<String> = fields
.iter()
.map(|(name, expr)| {
format!("{} := {}", aver_name_to_lean(name), emit_expr(expr, ctx))
})
.collect();
let lean_type_name = type_name.replace('.', "_");
format!("{{ {} : {} }}", parts.join(", "), lean_type_name)
}
ResolvedExpr::RecordUpdate {
type_name: _,
base,
updates,
..
} => {
let base_str = emit_expr(base, ctx);
let parts: Vec<String> = updates
.iter()
.map(|(name, expr)| {
format!("{} := {}", aver_name_to_lean(name), emit_expr(expr, ctx))
})
.collect();
format!("{{ {} with {} }}", base_str, parts.join(", "))
}
ResolvedExpr::TailCall { target, args } => {
let target_name = ctx.symbol_table.fn_entry(*target).key.name.clone();
let parts: Vec<String> = args.iter().map(|a| emit_expr_atom(a, ctx)).collect();
if parts.is_empty() {
aver_name_to_lean(&target_name)
} else {
format!("{} {}", aver_name_to_lean(&target_name), parts.join(" "))
}
}
}
}
fn emit_expr_atom(expr: &Spanned<ResolvedExpr>, ctx: &CodegenContext) -> String {
let s = emit_expr(expr, ctx);
match &expr.node {
ResolvedExpr::Literal(Literal::Int(i)) if *i < 0 => format!("({})", s),
ResolvedExpr::Literal(Literal::Float(f)) if *f < 0.0 => format!("({})", s),
ResolvedExpr::Literal(_)
| ResolvedExpr::Ident(_)
| ResolvedExpr::List(_)
| ResolvedExpr::Tuple(_)
| ResolvedExpr::IndependentProduct(_, _) => s,
_ => {
if s.starts_with('(')
|| s.starts_with('[')
|| s.starts_with('"')
|| s.starts_with('{')
|| !s.contains(' ')
{
s
} else {
format!("({})", s)
}
}
}
}
fn emit_literal(lit: &Literal) -> String {
match lit {
Literal::Int(i) => format!("{}", i),
Literal::Float(f) => {
let s = f.to_string();
if s.contains('.') {
s
} else {
format!("{}.0", s)
}
}
Literal::Str(s) => format!("\"{}\"", escape_lean_string(s)),
Literal::Bool(b) => if *b { "true" } else { "false" }.to_string(),
Literal::Unit => "()".to_string(),
}
}
fn escape_lean_string(s: &str) -> String {
crate::codegen::common::escape_string_literal(s)
}
fn emit_fn_call(
callee: &ResolvedCallee,
args: &[Spanned<ResolvedExpr>],
ctx: &CodegenContext,
) -> String {
match callee {
ResolvedCallee::Builtin(name) => {
if let Some(lean_code) = builtins::emit_builtin_call(name, args, ctx) {
return lean_code;
}
let arg_strs_owned: Vec<String> = args.iter().map(|a| emit_expr_atom(a, ctx)).collect();
match name.as_str() {
"BranchPath.child" if arg_strs_owned.len() == 2 => {
return format!(
"BranchPath.child {} {}",
arg_strs_owned[0], arg_strs_owned[1]
);
}
"BranchPath.parse" if arg_strs_owned.len() == 1 => {
return format!("BranchPath.parse {}", arg_strs_owned[0]);
}
_ => {}
}
if arg_strs_owned.is_empty() {
aver_name_to_lean(name)
} else {
format!("{} {}", aver_name_to_lean(name), arg_strs_owned.join(" "))
}
}
ResolvedCallee::Intrinsic(intr) => {
let arg_strs: Vec<String> = args.iter().map(|a| emit_expr_atom(a, ctx)).collect();
if arg_strs.is_empty() {
intr.name().to_string()
} else {
format!("{} {}", intr.name(), arg_strs.join(" "))
}
}
ResolvedCallee::Fn(fn_id) => {
let entry = ctx.symbol_table.fn_entry(*fn_id);
let bare = entry.key.name.as_str();
let module_prefix = entry.key.scope_str();
let arg_strs: Vec<String> = args.iter().map(|a| emit_expr_atom(a, ctx)).collect();
let func = match module_prefix {
Some(prefix) if !ctx.modules.is_empty() => {
format!("{}.{}", prefix, aver_name_to_lean(bare))
}
_ => aver_name_to_lean(bare),
};
if arg_strs.is_empty() {
func
} else {
format!("{} {}", func, arg_strs.join(" "))
}
}
ResolvedCallee::LocalSlot { name, .. } => {
let arg_strs: Vec<String> = args.iter().map(|a| emit_expr_atom(a, ctx)).collect();
let func = aver_name_to_lean(name);
if arg_strs.is_empty() {
func
} else {
format!("{} {}", func, arg_strs.join(" "))
}
}
ResolvedCallee::Unresolved { callee: inner } => {
let func = emit_expr(inner, ctx);
let arg_strs: Vec<String> = args.iter().map(|a| emit_expr_atom(a, ctx)).collect();
if arg_strs.is_empty() {
func
} else {
format!("{} {}", func, arg_strs.join(" "))
}
}
}
}
fn emit_constructor(
ctor: &ResolvedCtor,
args: &[Spanned<ResolvedExpr>],
ctx: &CodegenContext,
) -> String {
let inner_str = || -> String {
args.first()
.map(|a| emit_expr_atom(a, ctx))
.unwrap_or_else(|| "()".to_string())
};
match ctor {
ResolvedCtor::Builtin(BuiltinCtor::ResultOk) => format!("Except.ok {}", inner_str()),
ResolvedCtor::Builtin(BuiltinCtor::ResultErr) => {
format!("Except.error {}", inner_str())
}
ResolvedCtor::Builtin(BuiltinCtor::OptionSome) => format!("some {}", inner_str()),
ResolvedCtor::Builtin(BuiltinCtor::OptionNone) => "none".to_string(),
ResolvedCtor::User { type_id, name, .. } => {
let type_name = ctx.symbol_table.type_entry(*type_id).key.name.clone();
let variant = to_lower_first(name);
let arg_strs: Vec<String> = args.iter().map(|a| emit_expr_atom(a, ctx)).collect();
if arg_strs.is_empty() {
format!("{}.{}", type_name, variant)
} else {
format!("{}.{} {}", type_name, variant, arg_strs.join(" "))
}
}
ResolvedCtor::Unresolved { name } => {
format!("{} {}", name, inner_str())
}
}
}
fn emit_interpolated_str(parts: &[ResolvedStrPart], ctx: &CodegenContext) -> String {
if parts.is_empty() {
return "\"\"".to_string();
}
let mut result = String::new();
result.push_str("s!\"");
for part in parts {
match part {
ResolvedStrPart::Literal(s) => {
result.push_str(&escape_lean_string(s));
}
ResolvedStrPart::Parsed(expr) => {
result.push('{');
result.push_str(&emit_expr(expr, ctx));
result.push('}');
}
}
}
result.push('"');
result
}
fn emit_match(
subject: &Spanned<ResolvedExpr>,
arms: &[ResolvedMatchArm],
line: usize,
ctx: &CodegenContext,
) -> String {
if let Some((true_body, false_body)) = extract_bool_arms(arms) {
let cond = emit_expr(subject, ctx);
let t = emit_expr(true_body, ctx);
let f = emit_expr(false_body, ctx);
if true_body_uses_refinement_subtype(true_body, ctx) {
let hyp = format!("h_{line}");
return format!("if {hyp} : {cond} then {t}\n else {f}");
}
return format!("if {cond} then {t}\n else {f}");
}
let subj = emit_expr(subject, ctx);
let mut arm_strs = Vec::new();
for arm in arms {
let pat = emit_pattern(&arm.pattern);
let body = emit_expr(&arm.body, ctx);
if body.contains('\n') {
let body_lines: Vec<&str> = body.lines().collect();
let mut rendered = vec![format!(" | {} => {}", pat, body_lines[0])];
rendered.extend(
body_lines
.iter()
.skip(1)
.map(|line| format!(" {}", line)),
);
arm_strs.push(rendered.join("\n"));
} else {
arm_strs.push(format!(" | {} => {}", pat, body));
}
}
let needs_eq_binder = matches!(
&subject.node,
ResolvedExpr::Ident(_) | ResolvedExpr::Resolved { .. } | ResolvedExpr::Attr(_, _)
);
if needs_eq_binder {
let eq_name = format!("h_{}", line);
format!("match {} : {} with\n{}", eq_name, subj, arm_strs.join("\n"))
} else {
format!("match {} with\n{}", subj, arm_strs.join("\n"))
}
}
fn true_body_uses_refinement_subtype(expr: &Spanned<ResolvedExpr>, ctx: &CodegenContext) -> bool {
match &expr.node {
ResolvedExpr::RecordCreate { type_name, .. } => {
crate::codegen::common::find_refined_type(ctx, type_name)
.map(|decl| decl.carrier_type == "Int")
.unwrap_or(false)
}
ResolvedExpr::Call(_, args) => args
.iter()
.any(|a| true_body_uses_refinement_subtype(a, ctx)),
ResolvedExpr::Ctor(_, args) => args
.iter()
.any(|a| true_body_uses_refinement_subtype(a, ctx)),
ResolvedExpr::Attr(o, _) => true_body_uses_refinement_subtype(o, ctx),
ResolvedExpr::Match { arms, .. } => arms
.iter()
.any(|arm| true_body_uses_refinement_subtype(&arm.body, ctx)),
_ => false,
}
}
fn extract_bool_arms(
arms: &[ResolvedMatchArm],
) -> Option<(&Spanned<ResolvedExpr>, &Spanned<ResolvedExpr>)> {
if arms.len() != 2 {
return None;
}
let mut true_body = None;
let mut false_body = None;
for arm in arms {
match &arm.pattern {
ResolvedPattern::Literal(Literal::Bool(true)) => true_body = Some(arm.body.as_ref()),
ResolvedPattern::Literal(Literal::Bool(false)) => false_body = Some(arm.body.as_ref()),
_ => return None,
}
}
Some((true_body?, false_body?))
}
#[allow(dead_code)]
pub fn emit_stmt(stmt: &ResolvedStmt, ctx: &CodegenContext) -> String {
match stmt {
ResolvedStmt::Binding {
name,
ty_ann: _,
value,
} => {
let val = emit_expr(value, ctx);
format!("let {} := {}", aver_name_to_lean(name), val)
}
ResolvedStmt::Expr(expr) => emit_expr(expr, ctx),
}
}
pub fn emit_expr_legacy(
expr: &crate::ast::Spanned<crate::ast::Expr>,
ctx: &CodegenContext,
scope: Option<&str>,
) -> String {
let active = ctx.active_module_scope();
let effective = scope.or(active.as_deref());
let resolved = ctx.resolve_expr(expr, effective);
emit_expr(&resolved, ctx)
}
#[allow(dead_code)]
pub fn emit_stmt_legacy(
stmt: &crate::ast::Stmt,
ctx: &CodegenContext,
scope: Option<&str>,
) -> String {
let active = ctx.active_module_scope();
let effective = scope.or(active.as_deref());
let resolved = ctx.resolve_stmt(stmt, effective);
emit_stmt(&resolved, ctx)
}
#[allow(dead_code)]
pub fn emit_pattern_legacy(
pat: &crate::ast::Pattern,
ctx: &CodegenContext,
scope: Option<&str>,
) -> String {
let active = ctx.active_module_scope();
let effective = scope.or(active.as_deref());
let resolved = ctx.resolve_pattern(pat, effective);
super::pattern::emit_pattern(&resolved)
}
const LEAN_RESERVED: &[&str] = &[
"abbrev",
"axiom",
"by",
"calc",
"class",
"def",
"decreasing_by",
"deriving",
"do",
"else",
"end",
"example",
"from",
"fun",
"have",
"id",
"if",
"import",
"in",
"inductive",
"infix",
"infixl",
"infixr",
"instance",
"let",
"local",
"macro",
"match",
"mutual",
"namespace",
"noncomputable",
"nonrec",
"opaque",
"open",
"partial",
"postfix",
"prefix",
"priority",
"private",
"protected",
"public",
"repeat",
"return",
"scoped",
"section",
"show",
"structure",
"syntax",
"termination_by",
"then",
"theorem",
"toString",
"unsafe",
"where",
"with",
];
pub fn aver_name_to_lean(name: &str) -> String {
crate::codegen::common::escape_reserved_word(name, LEAN_RESERVED, "'")
}
#[cfg(test)]
mod tests {
use super::{aver_name_to_lean, escape_lean_string};
#[test]
fn aver_name_to_lean_escapes_lean_reserved_words() {
assert_eq!(aver_name_to_lean("repeat"), "repeat'");
assert_eq!(aver_name_to_lean("from"), "from'");
assert_eq!(aver_name_to_lean("by"), "by'");
assert_eq!(aver_name_to_lean("termination_by"), "termination_by'");
assert_eq!(aver_name_to_lean("value"), "value");
}
#[test]
fn escape_lean_string_escapes_control_chars() {
assert_eq!(escape_lean_string("\u{0008}\u{000C}"), "\\x08\\x0c");
assert_eq!(escape_lean_string("a\n\t\"\\z"), "a\\n\\t\\\"\\\\z");
}
}