use crate::{env::{ConstDecl, Env, IndDesc}, kernel, term::Term};
use super::{
ast::Decl,
core::{elab_expr, fold_lam, fold_pi, push_params, ElabState},
meta::ElabError,
};
pub fn elab_decl(st: &mut ElabState, env: &mut Env, decl: &Decl) -> Result<(), ElabError> {
match decl {
Decl::Def { name, params, ty, body }
| Decl::Theorem { name, params, ty, body } => {
elab_def(st, env, name, params, ty, body)
}
Decl::Axiom { name, ty } => {
let (ty_term, _) = elab_expr(st, env, ty)?;
let ty_closed = st.mctx.zonk(&ty_term);
if !ty_closed.is_kernel_term() {
return Err(ElabError::UnsolvedMeta(0));
}
let id = fnv_hash(name.as_bytes());
env.insert_const(id, ConstDecl { ty: ty_closed.clone(), body: None });
st.globals.insert(name.clone(), (Term::Const(id), ty_closed));
Ok(())
}
Decl::Inductive { name, params, sort, ctors } => {
elab_inductive(st, env, name, params, sort, ctors)
}
}
}
fn elab_def(
st: &mut ElabState,
env: &Env,
name: &str,
params: &[super::ast::Binder],
ty_expr: &super::ast::Expr,
body_expr: &super::ast::Expr,
) -> Result<(), ElabError> {
let n_before = st.locals.len();
let param_types = push_params(st, env, params)?;
let n_params = st.locals.len() - n_before;
let (ty_term, _) = elab_expr(st, env, ty_expr)?;
let (body_term, _) = elab_expr(st, env, body_expr)?;
let ctx = st.ctx();
let ty_z = st.mctx.zonk(&ty_term);
let body_z = st.mctx.zonk(&body_term);
if !ty_z.is_kernel_term() { return Err(ElabError::UnsolvedMeta(0)); }
if !body_z.is_kernel_term() { return Err(ElabError::UnsolvedMeta(0)); }
kernel::check(env, &ctx, &body_z, &ty_z)?;
for _ in 0..n_params { st.pop(); }
let closed_body = fold_lam(¶m_types, body_z);
let closed_ty = fold_pi (¶m_types, ty_z);
st.globals.insert(name.to_string(), (closed_body, closed_ty));
Ok(())
}
fn elab_inductive(
st: &mut ElabState,
env: &mut Env,
name: &str,
params: &[super::ast::Binder],
sort_expr: &super::ast::Expr,
ctors: &[(String, Box<super::ast::Expr>)],
) -> Result<(), ElabError> {
let n_before = st.locals.len();
let param_types = push_params(st, env, params)?;
let n_params = st.locals.len() - n_before;
let (sort_term, _) = elab_expr(st, env, sort_expr)?;
let u = match st.mctx.zonk(&sort_term) {
Term::Sort(u) => u,
other => return Err(ElabError::ExpectedSort(other)),
};
let ind_id = fnv_hash(name.as_bytes());
let param_tel = if param_types.is_empty() {
Term::Sort(0) } else {
fold_pi(¶m_types, Term::Sort(u))
};
let placeholder = IndDesc {
arity: n_params as u64,
sort: u,
param_tel: param_tel.clone(),
constructors: vec![],
};
let mut env2 = env.clone();
env2.insert(ind_id, placeholder);
let ind_term = Term::Ind(ind_id, (0..n_params as u64).map(Term::Var).collect());
let ind_ty = Term::Sort(u);
st.globals.insert(name.to_string(), (ind_term, ind_ty));
let mut ctor_tels: Vec<Term> = Vec::with_capacity(ctors.len());
for (ctor_name, ctor_ty_expr) in ctors {
let (ctor_term, _) = elab_expr(st, &env2, ctor_ty_expr)?;
let ctor_z = st.mctx.zonk(&ctor_term);
if !ctor_z.is_kernel_term() { return Err(ElabError::UnsolvedMeta(0)); }
check_positivity(ind_id, &ctor_z, ctor_name)?;
ctor_tels.push(ctor_z.clone());
let ctor_idx = (ctor_tels.len() - 1) as u64;
let (ctor_val, ctor_ty) = if n_params == 0 {
build_ctor_fn(ind_id, ctor_idx, &ctor_z)
} else {
(Term::Ctor(ind_id, ctor_idx, vec![]), Term::Sort(0))
};
st.globals.insert(format!("{}.{}", name, ctor_name), (ctor_val, ctor_ty));
}
for _ in 0..n_params { st.pop(); }
let desc = IndDesc {
arity: n_params as u64,
sort: u,
param_tel,
constructors: ctor_tels,
};
env.insert(ind_id, desc);
Ok(())
}
fn build_ctor_fn(id: u64, k: u64, ctor_tel: &Term) -> (Term, Term) {
let mut field_tys: Vec<Term> = Vec::new();
let mut t = ctor_tel;
loop {
match t {
Term::Pi(a, b) => { field_tys.push(*a.clone()); t = b; }
_ => break,
}
}
let n = field_tys.len();
let args: Vec<Term> = (0..n).map(|i| Term::Var((n - 1 - i) as u64)).collect();
let mut val = Term::Ctor(id, k, args);
for field_ty in field_tys.iter().rev() {
val = Term::Lam(Box::new(field_ty.clone()), Box::new(val));
}
(val, ctor_tel.clone())
}
fn check_positivity(ind_id: u64, t: &Term, ctor_name: &str) -> Result<(), ElabError> {
match t {
Term::Pi(a, b) => {
if contains_ind_negative(ind_id, a) {
return Err(ElabError::PositivityViolation(ctor_name.to_string()));
}
check_positivity(ind_id, b, ctor_name)
}
_ => Ok(()),
}
}
fn contains_ind_negative(ind_id: u64, t: &Term) -> bool {
match t {
Term::Ind(id, _) if *id == ind_id => false,
Term::Pi(a, b) => contains_ind(ind_id, a) || contains_ind_negative(ind_id, b),
_ => false,
}
}
fn contains_ind(id: u64, t: &Term) -> bool {
match t {
Term::Ind(i, ps) => *i == id || ps.iter().any(|p| contains_ind(id, p)),
Term::Var(_) | Term::Sort(_) | Term::Meta(_) => false,
Term::Pi(a, b) | Term::Lam(a, b) | Term::App(a, b) =>
contains_ind(id, a) || contains_ind(id, b),
Term::Let(a, v, b) =>
contains_ind(id, a) || contains_ind(id, v) || contains_ind(id, b),
Term::Ctor(_, _, args) => args.iter().any(|a| contains_ind(id, a)),
Term::Elim(_, m, cs, tg) =>
contains_ind(id, m) || cs.iter().any(|c| contains_ind(id, c)) || contains_ind(id, tg),
Term::EqSubst(p, h, pf) =>
contains_ind(id, p) || contains_ind(id, h) || contains_ind(id, pf),
Term::Const(_) => false,
}
}
pub fn fnv_hash(data: &[u8]) -> u64 {
let mut h: u64 = 0xcbf29ce484222325;
for b in data {
h ^= *b as u64;
h = h.wrapping_mul(0x100000001b3);
}
h
}