Compare commits

...

8 commits

Author SHA1 Message Date
Simon Cruanes
d5a7ff4c46
gitignore 2026-03-15 00:36:11 -04:00
Simon Cruanes
6fb4f7efe0 improve leancheck to handle #def 2026-03-15 04:35:59 +00:00
Simon Cruanes
588c128a55 makefile 2026-03-15 04:27:58 +00:00
Simon Cruanes
c5e7c11131 ocamlformat config 2026-03-15 04:27:52 +00:00
Simon Cruanes
93f3964d11 update ciclib+format 2026-03-15 04:27:32 +00:00
Simon Cruanes
7f96ed239d update leancheck+format 2026-03-15 04:27:25 +00:00
Simon Cruanes
08a7b7a3fd core-logic: format 2026-03-15 04:27:15 +00:00
Simon Cruanes
37411ed50f core-logic: add def_eq + whnf 2026-03-15 04:24:54 +00:00
24 changed files with 353 additions and 136 deletions

1
.gitignore vendored
View file

@ -18,3 +18,4 @@ perf.*
*.gz *.gz
.git-blame-ignore-revs .git-blame-ignore-revs
*.json *.json
*.tmp

View file

@ -1,4 +1,4 @@
version = 0.26.2 version = 0.27.0
profile=conventional profile=conventional
margin=80 margin=80
if-then-else=k-r if-then-else=k-r

View file

@ -30,6 +30,12 @@ test:
test-promote: test-promote:
@dune runtest $(OPTS) --force --no-buffer --auto-promote @dune runtest $(OPTS) --force --no-buffer --auto-promote
format:
@dune build @fmt --auto-promote
format-check:
@dune build @fmt --quiet
TESTOPTS ?= -j $(J) -c tests/benchpress.sexp --progress TESTOPTS ?= -j $(J) -c tests/benchpress.sexp --progress
TESTTOOL=benchpress TESTTOOL=benchpress
DATE=$(shell date +%FT%H:%M) DATE=$(shell date +%FT%H:%M)

View file

@ -1,6 +1,6 @@
(** Constants. (** Constants.
Constants are logical symbols, defined by the user thanks to an open type *) Constants are logical symbols, defined by the user thanks to an open type *)
open Types_ open Types_

View file

@ -39,16 +39,15 @@ val subst : subst -> t -> t
(** {2 Judgements} (** {2 Judgements}
These are little proofs of some symbolic properties of levels, even These are little proofs of some symbolic properties of levels, even those
those which contain variables. *) which contain variables. *)
val judge_leq : t -> t -> bool val judge_leq : t -> t -> bool
(** [judge_leq st l1 l2] is [true] if [l1] is proven to be lower (** [judge_leq st l1 l2] is [true] if [l1] is proven to be lower or equal to
or equal to [l2] *) [l2] *)
val judge_eq : t -> t -> bool val judge_eq : t -> t -> bool
(** [judge_eq st l1 l2] is [true] iff [judge_leq l1 l2] (** [judge_eq st l1 l2] is [true] iff [judge_leq l1 l2] and [judge_leq l2 l1] *)
and [judge_leq l2 l1] *)
val judge_is_zero : t -> bool val judge_is_zero : t -> bool
(** [judge_is_zero st l] is [true] iff [l <= 0] holds *) (** [judge_is_zero st l] is [true] iff [l <= 0] holds *)

View file

@ -190,9 +190,9 @@ let db_shift_ (e : term) (n : int) =
map_shallow e ~f:(fun inbind u -> map_shallow e ~f:(fun inbind u ->
loop u loop u
(if inbind then (if inbind then
k + 1 k + 1
else else
k)) k))
) )
in in
assert (n >= 0); assert (n >= 0);
@ -225,9 +225,9 @@ let db_replace_ e (env : t list) : term =
map_shallow e ~f:(fun inb u -> map_shallow e ~f:(fun inb u ->
aux u aux u
(if inb then (if inb then
k + 1 k + 1
else else
k)) k))
) )
in in
if is_closed e then if is_closed e then

View file

@ -1,11 +1,11 @@
(** Core logic terms. (** Core logic terms.
The core terms are expressions in the calculus of constructions, The core terms are expressions in the calculus of constructions, with no
with no universe polymorphism nor cumulativity. It should be fast, with hashconsing; universe polymorphism nor cumulativity. It should be fast, with hashconsing;
and simple enough (no inductives, no universe trickery). and simple enough (no inductives, no universe trickery).
It is intended to be the foundation for user-level terms and types and formulas. It is intended to be the foundation for user-level terms and types and
*) formulas. *)
open Types_ open Types_
@ -41,9 +41,9 @@ val as_type : t -> Level.t option
val as_type_exn : t -> Level.t val as_type_exn : t -> Level.t
val iter_shallow : f:(bool -> t -> unit) -> t -> unit val iter_shallow : f:(bool -> t -> unit) -> t -> unit
(** [iter_shallow f e] iterates on immediate subterms of [e], (** [iter_shallow f e] iterates on immediate subterms of [e], calling
calling [f trdb e'] for each subterm [e'], with [trdb = true] iff [f trdb e'] for each subterm [e'], with [trdb = true] iff [e'] is directly
[e'] is directly under a binder. *) under a binder. *)
val map_shallow : f:(bool -> t -> t) -> t -> t val map_shallow : f:(bool -> t -> t) -> t -> t
val exists_shallow : f:(bool -> t -> bool) -> t -> bool val exists_shallow : f:(bool -> t -> bool) -> t -> bool
@ -53,8 +53,8 @@ val is_type : t -> bool
(** [is_type t] is true iff [view t] is [Type _] *) (** [is_type t] is true iff [view t] is [Type _] *)
val is_closed : t -> bool val is_closed : t -> bool
(** Is the term closed (all bound variables are paired with a binder)? (** Is the term closed (all bound variables are paired with a binder)? time:
time: O(1) *) O(1) *)
(** {2 Creation} *) (** {2 Creation} *)
@ -75,8 +75,8 @@ val subst_level : Level.subst -> t -> t
(** De bruijn indices *) (** De bruijn indices *)
module DB : sig module DB : sig
val subst_db0 : t -> by:t -> t val subst_db0 : t -> by:t -> t
(** [subst_db0 store t ~by] replaces bound variable 0 in [t] with (** [subst_db0 store t ~by] replaces bound variable 0 in [t] with the term
the term [by]. This is useful, for example, to implement beta-reduction. [by]. This is useful, for example, to implement beta-reduction.
For example, with [t] being [_[0] = (\x. _[2] _[1] x[0])], For example, with [t] being [_[0] = (\x. _[2] _[1] x[0])],
[subst_db0 store t ~by:"hello"] is ["hello" = (\x. _[2] "hello" x[0])]. *) [subst_db0 store t ~by:"hello"] is ["hello" = (\x. _[2] "hello" x[0])]. *)
@ -84,9 +84,9 @@ module DB : sig
val subst_db_l : t -> t list -> t val subst_db_l : t -> t list -> t
val shift : t -> by:int -> t val shift : t -> by:int -> t
(** [shift t ~by] shifts all bound variables in [t] that are not (** [shift t ~by] shifts all bound variables in [t] that are not closed on, by
closed on, by amount [by] (which must be >= 0). amount [by] (which must be >= 0).
For example, with term [t] being [\x. _[1] _[2] x[0]], For example, with term [t] being [\x. _[1] _[2] x[0]], [shift t ~by:5] is
[shift t ~by:5] is [\x. _[6] _[7] x[0]]. *) [\x. _[6] _[7] x[0]]. *)
end end

View file

@ -26,5 +26,4 @@ let pp out (a : t) = a.c_ops.pp out a.c_view
let ser ~ser_t (self : t) = self.c_ops.ser ser_t self.c_view let ser ~ser_t (self : t) = self.c_ops.ser ser_t self.c_view
let make c_view c_ops ~ty:c_ty : t = { c_view; c_ops; c_ty } let make c_view c_ops ~ty:c_ty : t = { c_view; c_ops; c_ty }
type decoders = type decoders = (string * Ops.t * (term Ser_decode.t -> view Ser_decode.t)) list
(string * Ops.t * (term Ser_decode.t -> view Ser_decode.t)) list

View file

@ -1,6 +1,6 @@
(** Constants. (** Constants.
Constants are logical symbols, defined by the user thanks to an open type *) Constants are logical symbols, defined by the user thanks to an open type *)
open Types_ open Types_
@ -24,10 +24,8 @@ val make : view -> Ops.t -> ty:term -> t
val ser : ser_t:(term -> Ser_value.t) -> t -> string * Ser_value.t val ser : ser_t:(term -> Ser_value.t) -> t -> string * Ser_value.t
val ty : t -> term val ty : t -> term
type decoders = type decoders = (string * Ops.t * (term Ser_decode.t -> view Ser_decode.t)) list
(string * Ops.t * (term Ser_decode.t -> view Ser_decode.t)) list (** Decoders for constants: given a term store, return a list of supported tags,
(** Decoders for constants: given a term store, return a list and for each tag, a decoder for constants that have this particular tag. *)
of supported tags, and for each tag, a decoder for constants
that have this particular tag. *)
include EQ_HASH_PRINT with type t := t include EQ_HASH_PRINT with type t := t

View file

@ -42,16 +42,15 @@ val as_int : t -> int option
(** {2 Judgements} (** {2 Judgements}
These are little proofs of some symbolic properties of levels, even These are little proofs of some symbolic properties of levels, even those
those which contain variables. *) which contain variables. *)
val judge_leq : store -> t -> t -> bool val judge_leq : store -> t -> t -> bool
(** [judge_leq st l1 l2] is [true] if [l1] is proven to be lower (** [judge_leq st l1 l2] is [true] if [l1] is proven to be lower or equal to
or equal to [l2] *) [l2] *)
val judge_eq : store -> t -> t -> bool val judge_eq : store -> t -> t -> bool
(** [judge_eq st l1 l2] is [true] iff [judge_leq l1 l2] (** [judge_eq st l1 l2] is [true] iff [judge_leq l1 l2] and [judge_leq l2 l1] *)
and [judge_leq l2 l1] *)
val judge_is_zero : store -> t -> bool val judge_is_zero : store -> t -> bool
(** [judge_is_zero st l] is [true] iff [l <= 0] holds *) (** [judge_is_zero st l] is [true] iff [l <= 0] holds *)

85
src/core-logic/reduce.ml Normal file
View file

@ -0,0 +1,85 @@
open Types_
module T = Term
(* Pair-keyed table for def_eq cache *)
module T2_tbl = CCHashtbl.Make (struct
type t = term * term
let equal (a1, b1) (a2, b2) = T.equal a1 a2 && T.equal b1 b2
let hash (a, b) = CCHash.combine3 91 (T.hash a) (T.hash b)
end)
(** Weak head normal form. Beta-reduces at the head until stuck. Memoised via
[cache]. *)
let whnf ?(cache = T.Tbl.create 16) store e =
let rec loop e =
match T.Tbl.find_opt cache e with
| Some v -> v
| None ->
let v = step e in
T.Tbl.add cache e v;
v
and step e =
match T.view e with
| E_app (f, a) ->
let f' = loop f in
(match T.view f' with
| E_lam (_, _, body) -> loop (T.DB.subst_db0 store body ~by:a)
| _ ->
if f == f' then
e
else
T.app store f' a)
| _ -> e
in
loop e
(** Definitional equality: WHNF both sides, then compare structurally. Uses
[Level.judge_eq] for universe levels. Memoised via pair cache to handle
sharing in DAGs. *)
let def_eq store e1 e2 =
let whnf_cache = T.Tbl.create 16 in
let eq_cache : bool T2_tbl.t = T2_tbl.create 16 in
let whnf = whnf ~cache:whnf_cache store in
let rec go e1 e2 =
if T.equal e1 e2 then
true
else (
(* canonical order to halve cache size *)
let key =
if T.compare e1 e2 <= 0 then
e1, e2
else
e2, e1
in
match T2_tbl.find_opt eq_cache key with
| Some b -> b
| None ->
(* assume true while recursing (for coinductive guard on open terms) *)
T2_tbl.add eq_cache key true;
let r = check e1 e2 in
T2_tbl.replace eq_cache key r;
r
)
and check e1 e2 =
let e1 = whnf e1 in
let e2 = whnf e2 in
if T.equal e1 e2 then
true
else (
match T.view e1, T.view e2 with
| E_type l1, E_type l2 -> Level.judge_eq (T.Store.lvl_store store) l1 l2
| E_var v1, E_var v2 -> Var.equal v1 v2
| E_const c1, E_const c2 -> Const.equal c1 c2
| E_bound_var b1, E_bound_var b2 -> Bvar.equal b1 b2
| E_app (f1, a1), E_app (f2, a2) -> go f1 f2 && go a1 a2
| E_lam (_, ty1, b1), E_lam (_, ty2, b2) -> go ty1 ty2 && go b1 b2
| E_pi (_, ty1, b1), E_pi (_, ty2, b2) -> go ty1 ty2 && go b1 b2
| _ -> false
)
in
go e1 e2
(* Install into the kernel *)
let () = T.Internal_.def_eq_ref := def_eq

13
src/core-logic/reduce.mli Normal file
View file

@ -0,0 +1,13 @@
val whnf : ?cache:Term.t Term.Tbl.t -> Term.store -> Term.t -> Term.t
(** [whnf store e] reduces [e] to weak head normal form by beta-reducing at the
head until stuck. The optional [cache] is a memoisation table; pass the same
table across calls to amortise work over a DAG. *)
val def_eq : Term.store -> Term.t -> Term.t -> bool
(** [def_eq store a b] is true iff [a] and [b] are definitionally equal: both
sides are reduced to WHNF and then compared structurally, using
[Level.judge_eq] for universe levels. Results are memoised internally on the
shared DAG structure.
Note: this is installed as the kernel's equality check
([Term.Internal_.def_eq_ref]) when this module is loaded. *)

View file

@ -6,6 +6,7 @@ module Subst = Subst
module T_builtins = T_builtins module T_builtins = T_builtins
module Store = Term.Store module Store = Term.Store
module Level = Level module Level = Level
module Reduce = Reduce
(* TODO: move to separate library? *) (* TODO: move to separate library? *)
module Str_const = Str_const module Str_const = Str_const

View file

@ -1,7 +1,6 @@
(** Basic string constants. (** Basic string constants.
These constants are a string name, coupled with a type. These constants are a string name, coupled with a type. *)
*)
open Types_ open Types_

View file

@ -27,12 +27,12 @@ val is_eq : t -> bool
val is_bool : t -> bool val is_bool : t -> bool
val abs : store -> t -> bool * t val abs : store -> t -> bool * t
(** [abs t] returns an "absolute value" for the term, along with the (** [abs t] returns an "absolute value" for the term, along with the sign of
sign of [t]. [t].
The idea is that we want to turn [not a] into [(false, a)], The idea is that we want to turn [not a] into [(false, a)], or [(a != b)]
or [(a != b)] into [(false, a=b)]. For terms without a negation this into [(false, a=b)]. For terms without a negation this should return
should return [(true, t)]. *) [(true, t)]. *)
val as_bool_val : t -> bool option val as_bool_val : t -> bool option

View file

@ -404,9 +404,9 @@ module Make_ = struct
map_shallow_ e ~make ~f:(fun inbind u -> map_shallow_ e ~make ~f:(fun inbind u ->
loop u loop u
(if inbind then (if inbind then
k + 1 k + 1
else else
k)) k))
) )
in in
assert (n >= 0); assert (n >= 0);
@ -441,9 +441,9 @@ module Make_ = struct
map_shallow_ e ~make ~f:(fun inb u -> map_shallow_ e ~make ~f:(fun inb u ->
aux u aux u
(if inb then (if inb then
k + 1 k + 1
else else
k)) k))
in in
T_int_tbl.add cache_ (e, k) r; T_int_tbl.add cache_ (e, k) r;
r) r)
@ -454,8 +454,11 @@ module Make_ = struct
else else
aux e 0 aux e 0
(* TODO: have beta-reduction here; level checking; and pluggable reduction (* Definitional equality: installed by Reduce, defaults to syntactic equality *)
rules (in the store) so we can reduce quotients and recursors *) let def_eq_ref : (store -> term -> term -> bool) ref =
ref (fun _ a b -> equal a b)
(* TODO: pluggable reduction rules (in the store) for quotients and recursors *)
(** compute the type of [view]. *) (** compute the type of [view]. *)
let compute_ty_ (self : store) ~make (view : view) : term = let compute_ty_ (self : store) ~make (view : view) : term =
@ -485,9 +488,8 @@ module Make_ = struct
let ty_a = ty a in let ty_a = ty a in
(match ty_f.view with (match ty_f.view with
| E_pi (_, ty_arg_f, ty_bod_f) -> | E_pi (_, ty_arg_f, ty_bod_f) ->
(* check that the expected type matches *) (* check that the expected type matches, using definitional equality *)
(* FIXME: replace [equal] with definitional equality *) if not (!def_eq_ref self ty_arg_f ty_a) then
if not (equal ty_arg_f ty_a) then
Error.errorf Error.errorf
"@[<2>cannot @[apply `%a`@]@ @[to `%a`@],@ expected argument type: \ "@[<2>cannot @[apply `%a`@]@ @[to `%a`@],@ expected argument type: \
`%a`@ @[actual: `%a`@]@]" `%a`@ @[actual: `%a`@]@]"
@ -608,9 +610,9 @@ module Make_ = struct
map_shallow_ e ~make ~f:(fun inb u -> map_shallow_ e ~make ~f:(fun inb u ->
loop loop
(if inb then (if inb then
k + 1 k + 1
else else
k) k)
u) u)
| Some u -> | Some u ->
let u = db_shift_ ~make u k in let u = db_shift_ ~make u k in
@ -718,4 +720,6 @@ module Internal_ = struct
let subst_ store ~recursive t subst = let subst_ store ~recursive t subst =
subst_ ~make:(make_ store) ~recursive t subst subst_ ~make:(make_ store) ~recursive t subst
let def_eq_ref = def_eq_ref
end end

View file

@ -1,11 +1,11 @@
(** Core logic terms. (** Core logic terms.
The core terms are expressions in the calculus of constructions, The core terms are expressions in the calculus of constructions, with no
with no universe polymorphism nor cumulativity. It should be fast, with hashconsing; universe polymorphism nor cumulativity. It should be fast, with hashconsing;
and simple enough (no inductives, no universe trickery). and simple enough (no inductives, no universe trickery).
It is intended to be the foundation for user-level terms and types and formulas. It is intended to be the foundation for user-level terms and types and
*) formulas. *)
open Types_ open Types_
@ -19,9 +19,9 @@ type t = term
type store type store
(** The store for terms. (** The store for terms.
The store is responsible for allocating unique IDs to terms, and The store is responsible for allocating unique IDs to terms, and enforcing
enforcing their hashconsing (so that syntactic equality is just a pointer their hashconsing (so that syntactic equality is just a pointer comparison).
comparison). *) *)
(** View. (** View.
@ -61,31 +61,30 @@ val as_type : t -> Level.t option
val as_type_exn : t -> Level.t val as_type_exn : t -> Level.t
val iter_dag : ?seen:unit Tbl.t -> iter_ty:bool -> f:(t -> unit) -> t -> unit val iter_dag : ?seen:unit Tbl.t -> iter_ty:bool -> f:(t -> unit) -> t -> unit
(** [iter_dag t ~f] calls [f] once on each subterm of [t], [t] included. (** [iter_dag t ~f] calls [f] once on each subterm of [t], [t] included. It must
It must {b not} traverse [t] as a tree, but rather as a {b not} traverse [t] as a tree, but rather as a perfectly shared DAG.
perfectly shared DAG.
For example, in: For example, in:
{[ {[
let x = 2 in let x = 2 in
let y = f x x in let y = f x x in
let z = g y x in let z = g y x in
z = z z = z
]} ]}
the DAG has the following nodes: the DAG has the following nodes:
{[ n1: 2 {[
n2: f n1 n1 n1: 2
n3: g n2 n1 n2: f n1 n1
n4: = n3 n3 n3: g n2 n1
]} n4: = n3 n3
*) ]} *)
val iter_shallow : f:(bool -> t -> unit) -> t -> unit val iter_shallow : f:(bool -> t -> unit) -> t -> unit
(** [iter_shallow f e] iterates on immediate subterms of [e], (** [iter_shallow f e] iterates on immediate subterms of [e], calling
calling [f trdb e'] for each subterm [e'], with [trdb = true] iff [f trdb e'] for each subterm [e'], with [trdb = true] iff [e'] is directly
[e'] is directly under a binder. *) under a binder. *)
val map_shallow : store -> f:(bool -> t -> t) -> t -> t val map_shallow : store -> f:(bool -> t -> t) -> t -> t
val exists_shallow : f:(bool -> t -> bool) -> t -> bool val exists_shallow : f:(bool -> t -> bool) -> t -> bool
@ -101,12 +100,11 @@ val is_a_type : t -> bool
(** [is_a_type t] is true if [is_ty (ty t)] *) (** [is_a_type t] is true if [is_ty (ty t)] *)
val is_closed : t -> bool val is_closed : t -> bool
(** Is the term closed (all bound variables are paired with a binder)? (** Is the term closed (all bound variables are paired with a binder)? time:
time: O(1) *) O(1) *)
val has_fvars : t -> bool val has_fvars : t -> bool
(** Does the term contain free variables? (** Does the term contain free variables? time: O(1) *)
time: O(1) *)
val ty : t -> t val ty : t -> t
(** Return the type of this term. *) (** Return the type of this term. *)
@ -142,33 +140,33 @@ val open_lambda_exn : store -> t -> var * t
(** De bruijn indices *) (** De bruijn indices *)
module DB : sig module DB : sig
val lam_db : ?var_name:string -> store -> var_ty:t -> t -> t val lam_db : ?var_name:string -> store -> var_ty:t -> t -> t
(** [lam_db store ~var_ty bod] is [\ _:var_ty. bod]. Not DB shifting is done. *) (** [lam_db store ~var_ty bod] is [\ _:var_ty. bod]. Not DB shifting is done.
*)
val pi_db : ?var_name:string -> store -> var_ty:t -> t -> t val pi_db : ?var_name:string -> store -> var_ty:t -> t -> t
(** [pi_db store ~var_ty bod] is [pi _:var_ty. bod]. Not DB shifting is done. *) (** [pi_db store ~var_ty bod] is [pi _:var_ty. bod]. Not DB shifting is done.
*)
val subst_db0 : store -> t -> by:t -> t val subst_db0 : store -> t -> by:t -> t
(** [subst_db0 store t ~by] replaces bound variable 0 in [t] with (** [subst_db0 store t ~by] replaces bound variable 0 in [t] with the term
the term [by]. This is useful, for example, to implement beta-reduction. [by]. This is useful, for example, to implement beta-reduction.
For example, with [t] being [_[0] = (\x. _[2] _[1] x[0])], For example, with [t] being [_[0] = (\x. _[2] _[1] x[0])],
[subst_db0 store t ~by:"hello"] is ["hello" = (\x. _[2] "hello" x[0])]. [subst_db0 store t ~by:"hello"] is ["hello" = (\x. _[2] "hello" x[0])]. *)
*)
val shift : store -> t -> by:int -> t val shift : store -> t -> by:int -> t
(** [shift store t ~by] shifts all bound variables in [t] that are not (** [shift store t ~by] shifts all bound variables in [t] that are not closed
closed on, by amount [by] (which must be >= 0). on, by amount [by] (which must be >= 0).
For example, with term [t] being [\x. _[1] _[2] x[0]], For example, with term [t] being [\x. _[1] _[2] x[0]],
[shift store t ~by:5] is [\x. _[6] _[7] x[0]]. *) [shift store t ~by:5] is [\x. _[6] _[7] x[0]]. *)
val abs_on : store -> var -> t -> t val abs_on : store -> var -> t -> t
(** [abs_on store v t] is the term [t[v := _[0]]]. It replaces [v] with (** [abs_on store v t] is the term [t[v := _[0]]]. It replaces [v] with the
the bound variable with the same type as [v], and the DB index 0, bound variable with the same type as [v], and the DB index 0, and takes
and takes care of shifting if [v] occurs under binders. care of shifting if [v] occurs under binders.
For example, [abs_on store x (\y. x+y)] is [\y. _[1] y]. For example, [abs_on store x (\y. x+y)] is [\y. _[1] y]. *)
*)
end end
(**/**) (**/**)
@ -186,6 +184,10 @@ module Internal_ : sig
t -> t ->
f:(recurse:(t -> t) -> t -> t option) -> f:(recurse:(t -> t) -> t -> t option) ->
t t
val def_eq_ref : (store -> t -> t -> bool) ref
(** Definitional equality hook. Defaults to syntactic equality. Overwritten by
[Reduce] at init time. *)
end end
(**/**) (**/**)

View file

@ -0,0 +1,26 @@
module T = Sidekick_cic_lib.Term
module C = Sidekick_cic_lib.Const
let binder_to_cic : Lean_term.binder -> T.binder = function
| Lean_term.BD -> T.BD
| Lean_term.BI -> T.BI
| Lean_term.BS -> T.BS
| Lean_term.BC -> T.BC
(* ctx: list of elaborated types for BVar 0, 1, ... (innermost first) *)
let rec elab ~(ctx : T.t list) (lt : Lean_term.t) : T.t =
match lt with
| Lean_term.Sort l -> T.type_of_univ l
| Lean_term.BVar i -> T.bvar_i i
| Lean_term.Const (n, ls) -> T.const (C.make n) ls
| Lean_term.App (f, a) -> T.app (elab ~ctx f) (elab ~ctx a)
| Lean_term.Lam (b, n, ty, body) ->
let ty' = elab ~ctx ty in
let body' = elab ~ctx:(ty' :: ctx) body in
T.lam (binder_to_cic b) n ~var_ty:ty' body'
| Lean_term.Pi (b, n, ty, body) ->
let ty' = elab ~ctx ty in
let body' = elab ~ctx:(ty' :: ctx) body in
T.pi (binder_to_cic b) n ~var_ty:ty' body'
let elab_top lt = elab ~ctx:[] lt

View file

@ -0,0 +1,4 @@
val elab :
ctx:Sidekick_cic_lib.Term.t list -> Lean_term.t -> Sidekick_cic_lib.Term.t
val elab_top : Lean_term.t -> Sidekick_cic_lib.Term.t

View file

@ -0,0 +1,25 @@
module Level = Sidekick_cic_lib.Level
type binder = BD | BI | BS | BC
type t =
| Sort of Level.t
| BVar of int (* de Bruijn index only, no type *)
| Const of string * Level.t list
| App of t * t
| Lam of binder * string * t * t (* binder, name, var_ty, body *)
| Pi of binder * string * t * t
let dummy = Sort Level.one
let rec pp out = function
| Sort l -> Fmt.fprintf out "Sort(%a)" Level.pp l
| BVar i -> Fmt.fprintf out "BVar(%d)" i
| Const (n, []) -> Fmt.string out n
| Const (n, ls) ->
Fmt.fprintf out "(%s %a)" n (Fmt.list ~sep:(Fmt.return " ") Level.pp) ls
| App (f, a) -> Fmt.fprintf out "(%a %a)" pp f pp a
| Lam (_, n, ty, body) ->
Fmt.fprintf out "(fun (%s : %a). %a)" n pp ty pp body
| Pi (_, n, ty, body) ->
Fmt.fprintf out "(forall (%s : %a). %a)" n pp ty pp body

View file

@ -0,0 +1,14 @@
module Level = Sidekick_cic_lib.Level
type binder = BD | BI | BS | BC
type t =
| Sort of Level.t
| BVar of int
| Const of string * Level.t list
| App of t * t
| Lam of binder * string * t * t
| Pi of binder * string * t * t
val dummy : t
val pp : t Fmt.printer

View file

@ -7,7 +7,11 @@ let ( let@ ) = ( @@ )
(** Map indexes to objects *) (** Map indexes to objects *)
module Idx = struct module Idx = struct
type t = { names: string Vec.t; levels: Level.t Vec.t; terms: T.t Vec.t } type t = {
names: string Vec.t;
levels: Level.t Vec.t;
terms: Lean_term.t Vec.t;
}
(* create. The slot 0 is already filled with name "" *) (* create. The slot 0 is already filled with name "" *)
let create () : t = let create () : t =
@ -31,10 +35,10 @@ module Idx = struct
| exception _ -> Error.errorf "invalid name at %d" i | exception _ -> Error.errorf "invalid name at %d" i
let set_term self i t : unit = let set_term self i t : unit =
ensure_size self.terms ~dummy:T.type_ (i + 1); ensure_size self.terms ~dummy:Lean_term.dummy (i + 1);
Vec.set self.terms i t Vec.set self.terms i t
let get_term self i : T.t = let get_term self i : Lean_term.t =
match Vec.get self.terms i with match Vec.get self.terms i with
| s -> s | s -> s
| exception _ -> Error.errorf "invalid term at %d" i | exception _ -> Error.errorf "invalid term at %d" i
@ -54,7 +58,7 @@ module Idx = struct
"(@[idx {@ names: [@[%a@]],@ terms: [@[%a@]],@ levels: [@[%a@]]}@])" "(@[idx {@ names: [@[%a@]],@ terms: [@[%a@]],@ levels: [@[%a@]]}@])"
(Fmt.iter ~sep:(Fmt.return ";@ ") (pp_with_idx Fmt.Dump.string)) (Fmt.iter ~sep:(Fmt.return ";@ ") (pp_with_idx Fmt.Dump.string))
(Vec.to_iter self.names |> Iter.zip_i) (Vec.to_iter self.names |> Iter.zip_i)
(Fmt.iter ~sep:(Fmt.return ";@ ") (pp_with_idx T.pp_debug)) (Fmt.iter ~sep:(Fmt.return ";@ ") (pp_with_idx Lean_term.pp))
(Vec.to_iter self.terms |> Iter.zip_i) (Vec.to_iter self.terms |> Iter.zip_i)
(Fmt.iter ~sep:(Fmt.return ";@ ") Level.pp) (Fmt.iter ~sep:(Fmt.return ";@ ") Level.pp)
(Vec.to_iter self.levels) (Vec.to_iter self.levels)
@ -68,13 +72,13 @@ let name_join a b =
a ^ "." ^ b a ^ "." ^ b
let binder_of_string = function let binder_of_string = function
| "#BD" -> T.BD | "#BD" -> Lean_term.BD
| "#BI" -> T.BI | "#BI" -> Lean_term.BI
| "#BS" -> T.BS | "#BS" -> Lean_term.BS
| "#BC" -> T.BC | "#BC" -> Lean_term.BC
| s -> failwith (Printf.sprintf "invalid binder: %S" s) | s -> failwith (Printf.sprintf "invalid binder: %S" s)
let process_files ~max_err (files : string list) : unit = let process_files ~max_err ~quiet (files : string list) : unit =
let start = Mtime_clock.now () in let start = Mtime_clock.now () in
Log.debugf 1 (fun k -> Log.debugf 1 (fun k ->
@ -116,38 +120,45 @@ let process_files ~max_err (files : string list) : unit =
let up ~at i : unit = let up ~at i : unit =
Idx.set_level idx at (Level.var @@ Idx.get_name idx i) Idx.set_level idx at (Level.var @@ Idx.get_name idx i)
let ev ~at i : unit = Idx.set_term idx at (T.bvar_i i) let ev ~at i : unit = Idx.set_term idx at (Lean_term.BVar i)
let ea ~at i j : unit = let ea ~at i j : unit =
Idx.set_term idx at (T.app (Idx.get_term idx i) (Idx.get_term idx j)) Idx.set_term idx at
(Lean_term.App (Idx.get_term idx i, Idx.get_term idx j))
let ec ~at i_name i_args : unit = let ec ~at i_name i_args : unit =
let name = Idx.get_name idx i_name in let name = Idx.get_name idx i_name in
let args = List.map (Idx.get_level idx) i_args in let args = List.map (Idx.get_level idx) i_args in
Idx.set_term idx at (T.const (Const.make name) args) Idx.set_term idx at (Lean_term.Const (name, args))
let es ~at i : unit = let es ~at i : unit =
Idx.set_term idx at (T.type_of_univ (Idx.get_level idx i)) Idx.set_term idx at (Lean_term.Sort (Idx.get_level idx i))
let el ~at b n i j : unit = let el ~at b n i j : unit =
Idx.set_term idx at Idx.set_term idx at
(T.lam (binder_of_string b) (Idx.get_name idx n) (Lean_term.Lam
~var_ty:(Idx.get_term idx i) (Idx.get_term idx j)) ( binder_of_string b,
Idx.get_name idx n,
Idx.get_term idx i,
Idx.get_term idx j ))
let ep ~at b n i j : unit = let ep ~at b n i j : unit =
Idx.set_term idx at Idx.set_term idx at
(T.pi (binder_of_string b) (Idx.get_name idx n) (Lean_term.Pi
~var_ty:(Idx.get_term idx i) (Idx.get_term idx j)) ( binder_of_string b,
Idx.get_name idx n,
Idx.get_term idx i,
Idx.get_term idx j ))
let ind ~n_params ~nidx ~tyidx ~intros ~univ_params : unit = let ind ~n_params ~nidx ~tyidx ~intros ~univ_params : unit =
let name = Idx.get_name idx nidx in let name = Idx.get_name idx nidx in
let ty = Idx.get_term idx tyidx in let ty = Lean_elab.elab_top (Idx.get_term idx tyidx) in
let cstors = let cstors =
List.map List.map
(fun (i, j) -> (fun (i, j) ->
{ {
Inductive.c_name = Idx.get_name idx i; Inductive.c_name = Idx.get_name idx i;
c_ty = Idx.get_term idx j; c_ty = Lean_elab.elab_top (Idx.get_term idx j);
}) })
intros intros
in in
@ -157,12 +168,26 @@ let process_files ~max_err (files : string list) : unit =
let spec = { Inductive.name; ty; n_params; univ_params; cstors } in let spec = { Inductive.name; ty; n_params; univ_params; cstors } in
Fmt.eprintf "@[<2>@{<Green>Ind@}@ %a@]@." Inductive.pp_spec spec; if not quiet then (
(* TODO: register to the conversion relation *) Fmt.eprintf "@[<2>@{<Green>Ind@}@ %a@]@." Inductive.pp_spec spec;
Fmt.eprintf "is valid: %B@." (Inductive.is_valid spec); (* TODO: register to the conversion relation *)
Fmt.eprintf "is valid: %B@." (Inductive.is_valid spec)
);
() ()
let after_line () = Fmt.eprintf "%a@." Idx.dump idx let def ~nidx ~tyidx ~validx ~univ_params : unit =
let name = Idx.get_name idx nidx in
let _ty = Lean_elab.elab_top (Idx.get_term idx tyidx) in
let _val = Lean_elab.elab_top (Idx.get_term idx validx) in
let _univ_params = List.map (Idx.get_name idx) univ_params in
if not quiet then
Fmt.eprintf "@[<2>@{<Blue>Def@}@ %s@]@." name
let after_line () =
if quiet then (
if !n_lines mod 1000 = 0 then Fmt.eprintf "\rlines: %d%!" !n_lines
) else
Fmt.eprintf "%a@." Idx.dump idx
end) end)
in in
@ -179,6 +204,7 @@ let process_files ~max_err (files : string list) : unit =
let () = let () =
let files = ref [] in let files = ref [] in
let max_err = ref max_int in let max_err = ref max_int in
let quiet = ref false in
let opts = let opts =
[ [
"--debug", Arg.Int Log.set_debug, " set debug level"; "--debug", Arg.Int Log.set_debug, " set debug level";
@ -192,6 +218,10 @@ let () =
( "-nc", ( "-nc",
Arg.Unit (fun () -> CCFormat.set_color_default false), Arg.Unit (fun () -> CCFormat.set_color_default false),
" disable colors" ); " disable colors" );
( "-q",
Arg.Set quiet,
" quiet: suppress verbose output, show progress every 1000 lines" );
"--quiet", Arg.Set quiet, " like -q";
] ]
|> Arg.align |> Arg.align
in in
@ -199,4 +229,4 @@ let () =
Arg.parse opts (CCList.Ref.push files) "leancheck file+"; Arg.parse opts (CCList.Ref.push files) "leancheck file+";
if !files = [] then failwith "provide at least one file"; if !files = [] then failwith "provide at least one file";
process_files ~max_err:!max_err (List.rev !files) process_files ~max_err:!max_err ~quiet:!quiet (List.rev !files)

View file

@ -100,6 +100,15 @@ let parse ?(max_errors = max_int) (input : input) (cb : callback) : unit =
l l
in in
CB.ind ~n_params ~nidx ~tyidx ~intros ~univ_params CB.ind ~n_params ~nidx ~tyidx ~intros ~univ_params
| S "#DEF" :: I nidx :: I tyidx :: I validx :: tl ->
let univ_params =
List.map
(function
| I i -> i
| _ -> failwith "invalid param")
tl
in
CB.def ~nidx ~tyidx ~validx ~univ_params
| _ -> | _ ->
incr n_errors; incr n_errors;
Fmt.eprintf "@{<Yellow>warn@}: unhandled line %d: %s@." !n_line line Fmt.eprintf "@{<Yellow>warn@}: unhandled line %d: %s@." !n_line line

View file

@ -21,4 +21,7 @@ module type CALLBACK = sig
intros:(int * int) list -> intros:(int * int) list ->
univ_params:int list -> univ_params:int list ->
unit unit
val def :
nidx:int -> tyidx:int -> validx:int -> univ_params:int list -> unit
end end