first public release of the project

This commit is contained in:
Gabriel Scherer
2023-12-21 00:40:52 +01:00
parent 6c3e9720fc
commit 96bd055026
48 changed files with 2685 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
(** Constraint defines the type of type-inference constraints that our
solver understands -- see Solver.ml.
In theory this can let you perform type inference for many
different languages, as long as their typing rules can be
expressed by the constraints we have defined. In practice most
non-trivial language features will require extending the language
of constraints (and the solver) with new constructs. *)
(* We found it convenient to include some type definitions both inside
and outside the Make functor. Please don't let this small quirk
distract you. *)
module Types = struct
module Var = Utils.Variables()
type variable = Var.t
type structure = variable Structure.t
type ty =
| Var of variable
| Constr of structure
end
include Types
module Make (T : Utils.Functor) = struct
include Types
type eq_error =
| Clash of STLC.ty Utils.clash
| Cycle of variable Utils.cycle
(** A value of type [('a, 'e) t] is a constraint
whose resolution will either succeed, and produce
a witness of type ['a], or fail and produce
error information at type ['e].
In particular, type inference from an untyped language
can be formulated as a function of type
[untyped_term -> (typed_term, type_error) t].
This is a GADT (Generalized Algebraic Datatype). If
you are unfamiliar with function declarations of the
form
[let rec foo : type a e . (a, e) t -> ...]
then you should read the GADT chapter of the OCaml manual:
https://v2.ocaml.org/releases/5.1/htmlman/gadts-tutorial.html
*)
type ('ok, 'err) t =
| Ret : 'a on_sol -> ('a, 'e) t
| Err : 'e -> ('a, 'e) t
| Map : ('a, 'e) t * ('a -> 'b) -> ('b, 'e) t
| MapErr : ('a, 'e) t * ('e -> 'f) -> ('a, 'f) t
| Conj : ('a, 'e) t * ('b, 'e) t -> ('a * 'b, 'e) t
| Eq : variable * variable -> (unit, eq_error) t
| Exist : variable * structure option * ('a, 'e) t -> ('a, 'e) t
| Decode : variable -> (STLC.ty, variable Utils.cycle) t
| Do : ('a, 'e) t T.t -> ('a, 'e) t
and 'a on_sol = (variable -> STLC.ty) -> 'a
(** A value of type [('a, 'e) t] represents a part of an
inference constraint, but the value of type ['a] that
it produces on success may depend on the solution to
the whole constraint, not just for this part of the
constraint.
For example, consider the untyped term
[(lambda y. 42) 0]
The sub-constraint generated for [lambda y. 42] could
be resolved first by the constraint solver and found
satisfiable, but at this point we don't know what the
final type for [y] will be, it will be
a still-undetermined inference variable [?w]. The
actual type for [?w] will only be determined when
resolving other parts of the whole constraint that
handle the application of [0]. We have to solve the
whole constraint, and then come back to elaborate an
explictly-typed term [lambda (y : int). 42].
The solution to the whole constraint is represented by
a mapping from inference variables to elaborated
types.
*)
let (let+) c f = Map(c, f)
let (and+) c1 c2 = Conj(c1, c2)
(** These are "binding operators". Usage example:
{[
let+ ty1 = Decode w1
and+ ty2 = Decode w2
in Constr (Arrow (ty1, ty2))
]}
After desugaring the binding operators, this is equivalent to
{[
Map(Conj(Decode w1, Decode w2), fun (ty1, ty2) ->
Constr (Arrow (ty1, ty2)))
]}
For more details on binding operators, see
https://v2.ocaml.org/releases/5.1/manual/bindingops.html
*)
end
+20
View File
@@ -0,0 +1,20 @@
module Make(M : Utils.MonadPlus) = struct
module Untyped = Untyped.Make(M)
module Constraint = Constraint.Make(M)
module Infer = Infer.Make(M)
module Solver = Solver.Make(M)
(* just in case... *)
module TeVarSet = Untyped.Var.Set
module TyVarSet = STLC.TyVar.Set
let untyped : Untyped.term =
Do (M.delay (Utils.not_yet "Generator.untyped"))
let constraint_ : (STLC.term, Infer.err) Constraint.t =
Do (M.delay (Utils.not_yet "Generator.constraint_"))
let typed ~depth =
Utils.not_yet "Generator.typed" depth
end
+11
View File
@@ -0,0 +1,11 @@
module Make(M : Utils.MonadPlus) : sig
module Untyped := Untyped.Make(M)
module Constraint := Constraint.Make(M)
module Infer := Infer.Make(M)
val untyped : Untyped.term
val constraint_ : (STLC.term, Infer.err) Constraint.t
val typed : depth:int -> STLC.term M.t
end
+83
View File
@@ -0,0 +1,83 @@
(** Infer contains the logic to generate an inference constraint from
an untyped term, that will elaborate to an explicitly-typed term
or fail with a type error. *)
(* You have to implement the [has_type] function below,
which is the constraint-generation function. *)
module Make(T : Utils.Functor) = struct
module Constraint = Constraint.Make(T)
open Constraint
module Untyped = Untyped.Make(T)
(** The "environment" of the constraint generator maps each program
variable to an inference variable representing its (monomorphic)
type.
For example, to infer the type of the term [lambda x. t], we
will eventually call [has_type env t] with an environment
mapping [x] to a local inference variable representing its type.
*)
module Env = Untyped.Var.Map
type env = variable Env.t
type err = eq_error =
| Clash of STLC.ty Utils.clash
| Cycle of Constraint.variable Utils.cycle
type 'a constraint_ = ('a, err) Constraint.t
let eq v1 v2 = Eq(v1, v2)
let decode v = MapErr(Decode v, fun e -> Cycle e)
(** This is a helper function to implement constraint generation for
the [Annot] construct.
[bind ty k] takes a type [ty], and a constraint [k] parametrized
over a constraint variable. It creates a constraint context that
binds a new constraint variable [?w] that must be equal to [ty],
and places [k ?w] within this context.
For example, if [ty] is the type [?v1 -> (?v2 -> ?v3)] , then
[bind ty k] could be the constraint
[∃(?w1 = ?v2 -> ?v3). ∃(?w2 = ?v1 -> ?w1). k ?w2], or equivalently
[∃?w3 ?w4. ?w3 = ?v1 -> ?w4 ∧ ?w4 = ?v2 -> ?v3 ∧ k ?w3].
*)
let rec bind (ty : STLC.ty) (k : Constraint.variable -> ('a, 'e) t) : ('a, 'e) t =
(* Feel free to postpone implementing this function
until you implement the Annot case below. *)
Utils.not_yet "Infer.bind" (ty, k, fun () -> bind)
(** This function generates a typing constraint from an untyped term:
[has_type env t w] generates a constraint [C] which contains [w] as
a free inference variable, such that [C] has a solution if and only
if [t] is well-typed in [env], and in that case [w] is the type of [t].
For example, if [t] is the term [lambda x. x], then [has_type env t w]
generates a constraint equivalent to [∃?v. ?w = (?v -> ?v)].
Precondition: when calling [has_type env t], [env] must map each
term variable that is free in [t] to an inference variable.
*)
let rec has_type (env : env) (t : Untyped.term) (w : variable) : (STLC.term, err) t =
match t with
| Untyped.Var x ->
Utils.not_yet "Infer.has_type: Var case" (env, t, w, x)
| Untyped.App (t, u) ->
Utils.not_yet "Infer.has_type: App case" (env, t, u, fun () -> has_type)
| Untyped.Abs (x, t) ->
Utils.not_yet "Infer.has_type: Abs case" (env, x, t, fun () -> has_type)
| Untyped.Let (x, t, u) ->
Utils.not_yet "Infer.has_type: Let case" (env, x, t, u, fun () -> has_type)
| Untyped.Annot (t, ty) ->
Utils.not_yet "Infer.has_type: Let case" (env, t, ty, bind, fun () -> has_type)
| Untyped.Tuple ts ->
Utils.not_yet "Infer.has_type: Let case" (env, ts, fun () -> has_type)
| Untyped.LetTuple (xs, t, u) ->
Utils.not_yet "Infer.has_type: Let case" (env, xs, t, u, fun () -> has_type)
| Do p ->
(* Feel free to postone this until you start looking
at random generation. Getting type inference to
work on all the other cases is a good first step. *)
Utils.not_yet "Infer.has_type: Let case" (env, p, fun () -> has_type)
end
+18
View File
@@ -0,0 +1,18 @@
module Make(T : Utils.Functor) : sig
module Untyped := Untyped.Make(T)
module Constraint := Constraint.Make(T)
type err =
| Clash of STLC.ty Utils.clash
| Cycle of Constraint.variable Utils.cycle
type 'a constraint_ = ('a, err) Constraint.t
val eq : Constraint.variable -> Constraint.variable -> unit constraint_
val decode : Constraint.variable -> STLC.ty constraint_
type env = Constraint.variable Untyped.Var.Map.t
val has_type : env ->
Untyped.term -> Constraint.variable -> STLC.term constraint_
end
+25
View File
@@ -0,0 +1,25 @@
type 'a t = MRand_not_implemented_yet
let map (f : 'a -> 'b) (s : 'a t) : 'b t =
Utils.not_yet "MRand.map" (f, s)
let return (x : 'a) : 'a t =
Utils.not_yet "MRand.return" x
let bind (sa : 'a t) (f : 'a -> 'b t) : 'b t =
Utils.not_yet "MRand.bind" (sa, f)
let delay (f : unit -> 'a t) : 'a t =
Utils.not_yet "MRand.delay" (f ())
let sum (li : 'a t list) : 'a t =
Utils.not_yet "MRand.sum" li
let fail : 'a t =
MRand_not_implemented_yet
let one_of (vs : 'a array) : 'a t =
Utils.not_yet "MRand.one_of" vs
let run (s : 'a t) : 'a Seq.t =
Utils.not_yet "MRand.run" s
+1
View File
@@ -0,0 +1 @@
include Utils.MonadPlus
+25
View File
@@ -0,0 +1,25 @@
type 'a t = MSeq_not_implemented_yet
let map (f : 'a -> 'b) (s : 'a t) : 'b t =
Utils.not_yet "MSeq.map" (f, s)
let return (x : 'a) : 'a t =
Utils.not_yet "MSeq.return" x
let bind (sa : 'a t) (f : 'a -> 'b t) : 'b t =
Utils.not_yet "MSeq.bind" (sa, f)
let delay (f : unit -> 'a t) : 'a t =
Utils.not_yet "MSeq.delay" (f ())
let sum (li : 'a t list) : 'a t =
Utils.not_yet "MSeq.sum" li
let fail : 'a t =
MSeq_not_implemented_yet
let one_of (vs : 'a array) : 'a t =
Utils.not_yet "MSeq.one_of" vs
let run (s : 'a t) : 'a Seq.t =
Utils.not_yet "MSeq.run" s
+1
View File
@@ -0,0 +1 @@
include Utils.MonadPlus
+23
View File
@@ -0,0 +1,23 @@
(* A type of explicitly-typed terms. *)
module TyVar = Structure.TyVar
type 'v ty_ =
| Constr of ('v, 'v ty_) Structure.t_
type raw_ty = string ty_
type ty = TyVar.t ty_
let rec freshen_ty (Constr s) =
Constr (Structure.freshen freshen_ty s)
module TeVar = Utils.Variables ()
type term =
| Var of TeVar.t
| App of term * term
| Abs of TeVar.t * ty * term
| Let of TeVar.t * ty * term * term
| Annot of term * ty
| Tuple of term list
| LetTuple of (TeVar.t * ty) list * term * term
+62
View File
@@ -0,0 +1,62 @@
(*
As explained in the README.md ("Abstracting over an effect"),
this module as well as other modules is parametrized over
an arbitrary effect [T : Functor].
*)
module Make (T : Utils.Functor) = struct
module Constraint = Constraint.Make(T)
module SatConstraint = SatConstraint.Make(T)
module ConstraintSimplifier = ConstraintSimplifier.Make(T)
module ConstraintPrinter = ConstraintPrinter.Make(T)
type env = Unif.Env.t
type log = PPrint.document list
let make_logger c0 =
let logs = Queue.create () in
let c0_erased = SatConstraint.erase c0 in
let add_to_log env =
let doc =
c0_erased
|> ConstraintSimplifier.simplify env
|> ConstraintPrinter.print_sat_constraint
in
Queue.add doc logs
in
let get_log () =
logs |> Queue.to_seq |> List.of_seq
in
add_to_log, get_log
(** See [../README.md] ("High-level description") or [Solver.mli]
for a description of normal constraints and
our expectations regarding the [eval] function. *)
type ('a, 'e) normal_constraint =
| NRet of 'a Constraint.on_sol
| NErr of 'e
| NDo of ('a, 'e) Constraint.t T.t
let eval (type a e) ~log (env : env) (c0 : (a, e) Constraint.t)
: log * env * (a, e) normal_constraint
=
let add_to_log, get_log =
if log then make_logger c0
else ignore, (fun _ -> [])
in
(* We recommend calling the function [add_to_log] above
whenever you get an updated environment. Then call
[get_log] at the end to get a list of log message.
$ dune exec -- minihell --log-solver foo.test
will show a log that will let you see the evolution
of your input constraint (after simplification) as
the solver progresses, which is useful for debugging.
(You can also tweak this code temporarily to print stuff on
stderr right away if you need dirtier ways to debug.)
*)
Utils.not_yet "Solver.eval" (env, c0, add_to_log, get_log)
end
+40
View File
@@ -0,0 +1,40 @@
module Make (T : Utils.Functor) : sig
module Constraint := Constraint.Make(T)
type env = Unif.Env.t
type log = PPrint.document list
(** Normal constraints are the result
of solving constraints without computing
inside [Do p] nodes. *)
type ('a, 'e) normal_constraint =
| NRet of 'a Constraint.on_sol
(** A succesfully elaborated value.
(See Constraint.ml for exaplanations on [on_sol].) *)
| NErr of 'e
(** A failed/false constraint. *)
| NDo of ('a, 'e) Constraint.t T.t
(** A constraint whose evaluation encountered an effectful
constraint in a [Do p] node.
We propose an evaluation rule of the form
[eval E[Do p] = NDo E[p]]
where a [Do (p : ('a1, 'e1) Constraint.t T.t)] node placed
inside an evaluation context [E] bubbles "all the way to the
top" in the result. [E[p]] is defined by using [T.map] to lift
the context-plugging operation
[E[_] : ('a1, 'e1) Constraint.t -> ('a2, 'e2) Constraint.t]
*)
(** If [~log:true] is passed in input, collect a list of
intermediate steps (obtained from the solver
environment and the original constraint by
constraint simplification) as the constraint-solving
progresses. Otherwise the returned [log] will not be
used and could be returned empty. *)
val eval :
log:bool -> env -> ('a, 'e) Constraint.t ->
log * env * ('a, 'e) normal_constraint
end
+71
View File
@@ -0,0 +1,71 @@
(** Type-formers are defined explicit as type "structures".
Type structures ['a t] are parametric over the type of
their leaves. Typical tree-shaped representation of
types would use [ty t], a structure carrying types as
sub-expressions, but the types manipulated by the
constraint solver are so-called "shallow types" that
always use inference variables at the leaves. We cannot
write, say, [?w = α -> (β * γ)], one has to write
[∃?w1 ?w2 ?w3 ?w4.
?w = ?w1 -> ?w2
∧ ?w1 = α
∧ ?w2 = ?w3 * ?w4
∧ ?w3 = β
∧ ?w4 = γ] instead.
(The implementation goes through a first step [('v, 'a) t_]
that is also parametrized over a notion of type variable,
just like ['v Untyped.term] -- see the documentation there.)
*)
module TyVar = Utils.Variables()
type ('v, 'a) t_ =
| Var of 'v
(** Note: a type variable here represents a rigid/opaque/abstract type [α, β...],
not a flexible inference variable like [?w] in constraints.
For example, for two distinct type variables [α, β]
the term [(lambda x. x : αα) (y : β)] is always
ill-typed. *)
| Arrow of 'a * 'a
| Prod of 'a list
type 'a raw = (string, 'a) t_
type 'a t = (TyVar.t, 'a) t_
let iter f = function
| Var _alpha -> ()
| Arrow (t1, t2) -> f t1; f t2
| Prod ts -> List.iter f ts
let map f = function
| Var alpha -> Var alpha
| Arrow (t1, t2) -> Arrow (f t1, f t2)
| Prod ts -> Prod (List.map f ts)
let merge f s1 s2 =
Utils.not_yet "Structure.merge" (f, s1, s2)
let global_tyvar : string -> TyVar.t =
(* There are no binders for type variables, which are scoped
globally for the whole term. *)
let tenv = Hashtbl.create 5 in
fun alpha ->
match Hashtbl.find tenv alpha with
| alpha_var -> alpha_var
| exception Not_found ->
let alpha_var = TyVar.fresh alpha in
Hashtbl.add tenv alpha alpha_var;
alpha_var
let freshen freshen = function
| Var alpha -> Var (global_tyvar alpha)
| Arrow (t1, t2) -> Arrow (freshen t1, freshen t2)
| Prod ts -> Prod (List.map freshen ts)
let print p = function
| Var v -> TyVar.print v
| Prod ts -> Printer.product (List.map p ts)
| Arrow (t1, t2) -> Printer.arrow (p t1) (p t2)
+19
View File
@@ -0,0 +1,19 @@
module TyVar : module type of Utils.Variables()
type ('v, 'a) t_ =
| Var of 'v
| Arrow of 'a * 'a
| Prod of 'a list
type 'a raw = (string, 'a) t_
type 'a t = (TyVar.t, 'a) t_
val iter : ('a -> unit) -> ('v, 'a) t_ -> unit
val map : ('a -> 'b) -> ('v, 'a) t_ -> ('v, 'b) t_
val merge : ('a -> 'b -> 'c) -> 'a t -> 'b t -> 'c t option
val freshen : ('a -> 'b) -> 'a raw -> 'b t
val print : ('a -> PPrint.document) -> 'a t -> PPrint.document
+133
View File
@@ -0,0 +1,133 @@
(* There is nothing that you have to implement in this file/module,
and no particular need to read its implementation. On the other hand,
you want to understand the interface exposed in [Unif.mli] has it
is important to implement a constraint solver in Solver.ml. *)
module UF = UnionFind.Make(UnionFind.StoreMap)
type var = Constraint.variable
(* The internal representation in terms of union-find nodes. *)
type uvar = unode UF.rref
and unode = {
var: var;
data: uvar Structure.t option;
}
(* The user-facing representation hides union-find nodes,
replaced by the corresponding constraint variables. *)
type repr = {
var: var;
structure: var Structure.t option;
}
module Env : sig
type t = {
store: unode UF.store;
map: uvar Constraint.Var.Map.t;
}
val empty : t
val mem : var -> t -> bool
val add : var -> Constraint.structure option -> t -> t
val uvar : var -> t -> uvar
val repr : var -> t -> repr
end = struct
type t = {
store: unode UF.store;
map: uvar Constraint.Var.Map.t;
}
let empty =
let store = UF.new_store () in
let map = Constraint.Var.Map.empty in
{ store; map }
let uvar var env : uvar =
Constraint.Var.Map.find var env.map
let mem var env =
Constraint.Var.Map.mem var env.map
let add var structure env =
let data = Option.map (Structure.map (fun v -> uvar v env)) structure in
let uvar = UF.make env.store { var; data } in
{ env with map = Constraint.Var.Map.add var uvar env.map }
let repr var env =
let { var; data; } = UF.get env.store (uvar var env) in
let var_of_uvar uv = (UF.get env.store uv).var in
let structure = Option.map (Structure.map var_of_uvar) data in
{ var; structure; }
end
type clash = var Utils.clash
exception Clash of clash
exception Cycle of var Utils.cycle
type err =
| Clash of clash
| Cycle of var Utils.cycle
let check_no_cycle env v =
let open struct
type status = Visiting | Visited
end in
let table = Hashtbl.create 42 in
let rec loop v =
let n = UF.get env.Env.store v in
match Hashtbl.find table n.var with
| Visited ->
()
| Visiting ->
raise (Cycle (Utils.Cycle n.var))
| exception Not_found ->
Hashtbl.replace table n.var Visiting;
Option.iter (Structure.iter loop) n.data;
Hashtbl.replace table n.var Visited;
in loop v
let rec unify orig_env v1 v2 : (Env.t, err) result =
let env = { orig_env with Env.store = UF.copy orig_env.Env.store } in
let queue = Queue.create () in
Queue.add (Env.uvar v1 env, Env.uvar v2 env) queue;
match unify_uvars env.Env.store queue with
| exception Clash clash -> Error (Clash clash)
| () ->
match check_no_cycle env (Env.uvar v1 env) with
| exception Cycle v -> Error (Cycle v)
| () -> Ok env
and unify_uvars store (queue : (uvar * uvar) Queue.t) =
match Queue.take_opt queue with
| None -> ()
| Some (u1, u2) ->
ignore (UF.merge store (merge queue) u1 u2);
unify_uvars store queue
and merge queue (n1 : unode) (n2 : unode) : unode =
let clash () = raise (Clash (n1.var, n2.var)) in
let data =
match n1.data, n2.data with
| None, None -> None
| None, (Some _ as d) | (Some _ as d), None -> d
| Some st1, Some st2 ->
match
Structure.merge (fun v1 v2 ->
Queue.add (v1, v2) queue;
v1
) st1 st2
with
| None -> clash ()
| Some d -> Some d
in
{ n1 with data }
let unifiable env v1 v2 =
match unify env v1 v2 with
| Ok _ -> true
| Error _ -> false
+62
View File
@@ -0,0 +1,62 @@
(** The Unif module provides unification, which is a key ingredient of
type inference. This is exposed as a persistent "equation
environment" [Unif.Env.t] that stores the current knowledge on
inference variables obtained by constraint evaluation:
- which inference variables are equal to each other
- for each inference variable, its known structure (if any)
*)
type var = Constraint.variable
type repr = {
var: var;
structure: Constraint.structure option;
}
(** [repr] represents all the knowledge so far about an inference
variable, or rather an equivalence class of inference variables
that are equal to each other:
- [var] is a choice of canonical representant for the equivalence class
- [structure] is the known structure (if any) of these variables
*)
module Env : sig
type t
val empty : t
val mem : var -> t -> bool
val add : var -> Constraint.structure option -> t -> t
(** [repr x env] gets the representant of [x] in [env].
@raise [Not_found] if [x] is not bound in [env]. *)
val repr : var -> t -> repr
end
(** Unification errors:
- [Clash] indicates that we tried to
unify two variables with incompatible structure
-- equating them would make the context inconsistent.
It returns the pair of variables with incompatible structure.
- [Cycle] indicates that unifying two variables
would introduce a cyclic, infinite type.
It returns one variable belonging to the prospective cycle.
*)
type err =
| Clash of var Utils.clash
| Cycle of var Utils.cycle
val unify : Env.t -> var -> var -> (Env.t, err) result
(** [unify env v1 v2] takes the current equation environment [env],
and tries to update it with the knowledge that [v1], [v2] must be
equal. If this equality would introduce an error, we fail with the
error report, otherwise we return the updated equation
environment. *)
val unifiable : Env.t -> var -> var -> bool
(** [unifiable env v1 v2] tests if unifying [v1] and [v2]
in the equation environment [env] would succeed. *)
+63
View File
@@ -0,0 +1,63 @@
(** Our syntax of untyped terms.
As explained in the README.md ("Abstracting over an effect"),
this module as well as other modules is parametrized over
an arbitrary effect [T : Functor].
*)
module Make(T : Utils.Functor) = struct
module Var = STLC.TeVar
(** ['t term_] is parametrized over the representation
of term variables. Most of the project code will
work with the non-parametrized instance [term] below. *)
type 't term_ =
| Var of 'tev
| App of 't term_ * 't term_
| Abs of 'tev * 't term_
| Let of 'tev * 't term_ * 't term_
| Tuple of 't term_ list
| LetTuple of 'tev list * 't term_ * 't term_
| Annot of 't term_ * 'tyv STLC.ty_
| Do of 't term_ T.t
constraint 't = < tevar : 'tev; tyvar : 'tyv; >
(** [raw_term] are terms with raw [string] for their
variables. Several binders may use the same
variable. These terms are produced by the parser. *)
type raw_term = < tevar : string; tyvar : string > term_
(** [term] are terms using [STLC.TeVar.t] variables,
which include a unique stamp to guarantee uniqueness
of binders. This is what most of the code manipulates. *)
type term = < tevar : Var.t; tyvar : Structure.TyVar.t; > term_
let freshen : raw_term -> term =
let module Env = Map.Make(String) in
let bind env x =
let x_var = Var.fresh x in
let env = Env.add x x_var env in
env, x_var
in
let rec freshen env =
function
| Var x -> Var (Env.find x env)
| App (t1, t2) -> App (freshen env t1, freshen env t2)
| Abs (x, t) ->
let env, x = bind env x in
Abs (x, freshen env t)
| Let (x, t1, t2) ->
let env_inner, x = bind env x in
Let (x, freshen env t1, freshen env_inner t2)
| Tuple ts ->
Tuple (List.map (freshen env) ts)
| LetTuple (xs, t1, t2) ->
let env_inner, xs = List.fold_left_map bind env xs in
LetTuple (xs, freshen env t1, freshen env_inner t2)
| Annot (t, ty) ->
Annot (freshen env t, STLC.freshen_ty ty)
| Do p ->
Do (T.map (freshen env) p)
in
fun t -> freshen Env.empty t
end
+122
View File
@@ -0,0 +1,122 @@
type 'a clash = 'a * 'a
type 'v cycle = Cycle of 'v [@@unboxed]
let string_of_doc doc =
let buf = Buffer.create 128 in
PPrint.ToBuffer.pretty 0.9 80 buf doc;
Buffer.contents buf
module Variables () : sig
type t = private {
name: string;
stamp: int;
}
val compare : t -> t -> int
val eq : t -> t -> bool
val fresh : string -> t
val namegen : string array -> (unit -> t)
val name : t -> string
val print : t -> PPrint.document
module Set : Set.S with type elt = t
module Map : Map.S with type key = t
end = struct
type t = {
name: string;
stamp: int;
}
let name v = v.name
let compare = Stdlib.compare
let eq n1 n2 = (compare n1 n2 = 0)
let stamps = Hashtbl.create 42
let fresh name =
let stamp =
match Hashtbl.find_opt stamps name with
| None -> 0
| Some n -> n
in
Hashtbl.replace stamps name (stamp + 1);
{ name; stamp; }
let namegen names =
if names = [||] then failwith "namegen: empty names array";
let counter = ref 0 in
let wrap n = n mod (Array.length names) in
fun () ->
let idx = !counter in
counter := wrap (!counter + 1);
fresh names.(idx)
let print { name; stamp } =
if stamp = 0 then PPrint.string name
else Printf.ksprintf PPrint.string "%s/%x" name stamp
module Key = struct
type nonrec t = t
let compare = compare
end
module Set = Set.Make(Key)
module Map = Map.Make(Key)
end
module type Functor = sig
type 'a t
val map : ('a -> 'b) -> 'a t -> 'b t
end
(** A signature for search monads, that represent
computations that enumerate zero, one or several
values. *)
module type MonadPlus = sig
include Functor
val return : 'a -> 'a t
val bind : 'a t -> ('a -> 'b t) -> 'b t
val sum : 'a t list -> 'a t
val fail : 'a t
val one_of : 'a array -> 'a t
(** [fail] and [one_of] can be derived from [sum], but
they typically have simpler and more efficient
specialized implementations. *)
val delay : (unit -> 'a t) -> 'a t
(** Many search monad implementations perform their computation
on-demand, when elements are requested, instead of forcing
computation already to produce the ['a t] value.
In a strict language, it is easy to perform computation
too early in this case, for example
[M.sum [foo; bar]] will compute [foo] and [bar] eagerly
even though [bar] may not be needed if we only observe
the first element.
The [delay] combinator makes this on-demand nature
explicit, for example one can write [M.delay
(fun () -> M.sum [foo; bar])] to avoid computing [foo]
and [bar] too early. Of course, if the underlying
implementation is in fact eager, then this may apply
the function right away.*)
val run : 'a t -> 'a Seq.t
(** ['a Seq.t] is a type of on-demand sequences from the
OCaml standard library:
https://v2.ocaml.org/api/Seq.html
*)
end
module Empty = struct
type 'a t = | (* the empty type *)
let map (_ : 'a -> 'b) : 'a t -> 'b t = function
| _ -> .
end
module _ = (Empty : Functor)
let not_yet fname = fun _ -> failwith (fname ^ ": not implemented yet")
+9
View File
@@ -0,0 +1,9 @@
(include_subdirs unqualified) ;; also look in subdirectories
(library
(name constrained_generation)
(public_name constrained_generation)
(synopsis "A random program generator based on constraint solving")
(libraries unionFind pprint)
(wrapped false)
)
+54
View File
@@ -0,0 +1,54 @@
module Make(T : Utils.Functor) = struct
open Constraint.Make(T)
open SatConstraint.Make(T)
let print_var v =
Printer.inference_variable
(Constraint.Var.print v)
let print_sat_constraint (c : sat_constraint) : PPrint.document =
let rec print_top =
fun c -> print_left_open c
and print_left_open =
let _print_self = print_left_open
and print_next = print_conj in
fun ac ->
let rec peel = function
| Exist (v, s, c) ->
let binding =
(print_var v,
Option.map (Structure.print print_var) s)
in
let (bindings, body) = peel c in
(binding :: bindings, body)
| other -> ([], print_next other)
in
let (bindings, body) = peel ac in
Printer.exist bindings body
and print_conj =
let _print_self = print_conj
and print_next = print_atom in
function
| Conj cs -> Printer.conjunction (List.map print_next cs)
| other -> print_next other
and print_atom =
function
| Decode v -> Printer.decode (print_var v)
| False -> Printer.false_
| Eq (v1, v2) ->
Printer.eq
(print_var v1)
(print_var v2)
| Do _ -> Printer.do_
| (Exist _ | Conj _) as other ->
PPrint.parens (print_top other)
in print_top c
let print_constraint (type a e) (c : (a, e) Constraint.t)
: PPrint.document
= print_sat_constraint (erase c)
end
+7
View File
@@ -0,0 +1,7 @@
module Make(T : Utils.Functor) : sig
module Constraint := Constraint.Make(T)
module SatConstraint := SatConstraint.Make(T)
val print_sat_constraint : SatConstraint.t -> PPrint.document
val print_constraint : ('a, 'e) Constraint.t -> PPrint.document
end
+79
View File
@@ -0,0 +1,79 @@
module Make(T : Utils.Functor) = struct
open Constraint.Make(T)
open SatConstraint.Make(T)
type env = Unif.Env.t
let simplify (env : env) (c : sat_constraint) : sat_constraint =
let is_in_env v = Unif.Env.mem v env in
let normalize v =
match Unif.Env.repr v env with
| { var; _ } -> var
| exception Not_found -> v
in
let module VarSet = Constraint.Var.Set in
let exist v s (fvs, c) : VarSet.t * sat_constraint =
assert (Var.eq v (normalize v));
let s =
match Unif.Env.repr v env with
| exception Not_found -> s
| { structure; _ } -> structure
in
let fvs =
let fvs = ref fvs in
Option.iter (Structure.iter (fun v -> fvs := VarSet.add v !fvs)) s;
!fvs in
VarSet.remove v fvs,
Exist (v, s, c)
in
let rec simpl (bvs : VarSet.t) (c : sat_constraint) : VarSet.t * sat_constraint =
match c with
| False ->
(* Note: we do not attempt to normalize (⊥ ∧ C) into ⊥, (∃w. ⊥)
into ⊥, etc. If a contradiction appears in the constraint, we
think that it is clearer to see it deep in the constraint
term, in the context where the solver found it, rather than
bring it all the way to the top and erasing the rest of the
constraint in the process. *)
VarSet.empty, False
| Conj cs ->
let (fvs, cs) =
List.fold_left (fun (fvs, cs) c ->
let (fvs', c) = simpl bvs c in
let cs' = match c with
| Conj cs' -> cs'
| _ -> [c]
in
(VarSet.union fvs' fvs, cs' @ cs)
) (VarSet.empty, []) cs in
fvs, Conj cs
| Eq (v1, v2) ->
let v1, v2 = normalize v1, normalize v2 in
if Constraint.Var.eq v1 v2 then
VarSet.empty, Conj [] (* True *)
else begin match Unif.unifiable env v1 v2 with
| false ->
VarSet.empty, False
| true | exception Not_found ->
VarSet.of_list [v1; v2], Eq (v1, v2)
end
| Exist (v, s, c) ->
let fvs, c = simpl (VarSet.add v bvs) c in
if is_in_env v then (fvs, c)
else if not (VarSet.mem v fvs) then (fvs, c)
else exist v s (fvs, c)
| Decode v ->
let v = normalize v in
VarSet.singleton v, Decode v
| Do p ->
bvs, Do p
in
let rec add_exist (fvs, c) =
match VarSet.choose_opt fvs with
| None -> c
| Some v ->
add_exist (exist v None (fvs, c))
in
add_exist (simpl VarSet.empty c)
end
+6
View File
@@ -0,0 +1,6 @@
module Make(T : Utils.Functor) : sig
type env = Unif.Env.t
module SatConstraint := SatConstraint.Make(T)
val simplify : env -> SatConstraint.t -> SatConstraint.t
end
+35
View File
@@ -0,0 +1,35 @@
type env = Unif.Env.t
type slot =
| Ongoing
| Done of STLC.ty
let new_var =
STLC.TyVar.namegen [|"α"; "β"; "γ"; "δ"|]
let table = Hashtbl.create 42
let decode (env : env) (v : Constraint.variable) : STLC.ty =
let exception Found_cycle of Constraint.variable Utils.cycle in
let rec decode (v : Constraint.variable) : STLC.ty =
let repr = Unif.Env.repr v env in
begin match Hashtbl.find table repr.var with
| Done ty -> ty
| Ongoing -> raise (Found_cycle (Utils.Cycle repr.var))
| exception Not_found ->
Hashtbl.replace table repr.var Ongoing;
let ty =
STLC.Constr (
match repr.structure with
| Some s -> Structure.map decode s
| None -> Var (new_var ())
)
in
Hashtbl.replace table repr.var (Done ty);
ty
end
in
(* Because we perform an occur-check on unification, we can assume
that we never find any cycle during decoding:
[Found_cycle] should never be raised here. *)
decode v
+3
View File
@@ -0,0 +1,3 @@
type env = Unif.Env.t
val decode : env -> Constraint.variable -> STLC.ty
+113
View File
@@ -0,0 +1,113 @@
open PPrint
(** ?w *)
let inference_variable w =
string "?" ^^ w
(** $t -> $u *)
let arrow t u = group @@
t ^/^ string "->" ^/^ u
(** {$t1 * $t2 * ... $tn} *)
let product ts = group @@
braces (separate (break 1 ^^ star ^^ space) ts)
(** ($term : $ty) *)
let annot term ty = group @@
surround 2 0 lparen (
term ^/^ colon ^//^ ty
) rparen
(** lambda $input. $body *)
let lambda ~input ~body = group @@
string "lambda"
^/^ input
^^ string "."
^//^ body
(** let $var = $def in $body *)
let let_ ~var ~def ~body = group @@
string "let"
^/^ var
^/^ string "="
^/^ def
^/^ string "in"
^//^ body
(** $t $u *)
let app t u = group @@
t ^//^ u
(** (t1, t2... tn) *)
let tuple p ts = group @@
match ts with
| [] -> lparen ^^ rparen
| _ ->
surround 2 0 lparen (
match ts with
| [t] ->
(* For arity-1 tuples we print (foo,)
instead of (foo) which would be ambiguous. *)
p t ^^ comma
| _ ->
separate_map (comma ^^ break 1) p ts
) rparen
(** ∃$w1 $w2 ($w3 = $s) $w4... $wn. $c *)
let exist bindings body = group @@
let print_binding (w, s) =
match s with
| None -> w
| Some s ->
group @@
surround 2 0 lparen (
w
^/^ string "="
^/^ s
) rparen
in
let bindings =
group (flow_map (break 1) print_binding bindings)
in
group (utf8string "" ^^ ifflat empty space
^^ nest 2 bindings
^^ break 0 ^^ string ".")
^^ prefix 2 1 empty body
let true_ = utf8string ""
let false_ = utf8string ""
(** $c1 ∧ $c2 ∧ .... ∧ $cn *)
let conjunction docs = group @@
match docs with
| [] -> true_
| docs -> separate (break 1 ^^ utf8string "" ^^ space) docs
(** $v1 = $v2 *)
let eq v1 v2 = group @@
v1
^/^ string "="
^/^ v2
(** decode $v *)
let decode v = group @@
string "decode" ^^ break 1 ^^ v
let do_ = string "do?"
(**
$ty1
incompatible with
$ty2
*)
let incompatible ty1 ty2 =
group (blank 2 ^^ nest 2 ty1)
^^ hardline ^^ string "incompatible with" ^^ hardline ^^
group (blank 2 ^^ nest 2 ty2)
let cycle v =
string "cycle on constraint variable" ^/^ v
let with_header header doc =
string header ^^ colon ^^ nest 2 (group (hardline ^^ doc))
+65
View File
@@ -0,0 +1,65 @@
open STLC
let print_ty : ty -> PPrint.document =
let rec print t =
let print_self = print
and print_next = print_atom in
match t with
| Constr (Arrow (t1, t2)) ->
Printer.arrow (print_next t1) (print_self t2)
| other -> print_next other
and print_atom = function
| Constr (Var alpha) -> TyVar.print alpha
| Constr (Prod ts) -> Printer.product (List.map print ts)
| Constr (Arrow _) as other -> PPrint.parens (print other)
in print
let print_term : term -> PPrint.document =
let print_binding x tau =
Printer.annot (TeVar.print x) (print_ty tau)
in
let rec print_top t = print_left_open t
and print_left_open t =
let print_self = print_left_open
and print_next = print_app in
PPrint.group @@ match t with
| Abs (x, tau, t) ->
Printer.lambda
~input:(print_binding x tau)
~body:(print_self t)
| Let (x, tau, t, u) ->
Printer.let_
~var:(print_binding x tau)
~def:(print_top t)
~body:(print_self u)
| LetTuple (xtaus, t, u) ->
Printer.let_
~var:(Printer.tuple (fun (x, tau) -> print_binding x tau) xtaus)
~def:(print_top t)
~body:(print_self u)
| other -> print_next other
and print_app t =
let print_self = print_app
and print_next = print_atom in
PPrint.group @@ match t with
| App (t, u) ->
Printer.app (print_self t) (print_next u)
| other -> print_next other
and print_atom t =
PPrint.group @@ match t with
| Var x -> TeVar.print x
| Annot (t, ty) ->
Printer.annot
(print_top t)
(print_ty ty)
| Tuple ts ->
Printer.tuple print_top ts
| (App _ | Abs _ | Let _ | LetTuple _) as other ->
PPrint.parens (print_top other)
in print_top
+2
View File
@@ -0,0 +1,2 @@
val print_ty : STLC.ty -> PPrint.document
val print_term : STLC.term -> PPrint.document
+43
View File
@@ -0,0 +1,43 @@
module Make (T : Utils.Functor) = struct
module Constraint = Constraint.Make(T)
open Constraint
type t = sat_constraint
and sat_constraint =
| Exist of variable * structure option * sat_constraint
| Conj of sat_constraint list (* [True] is [Conj []] *)
| Eq of variable * variable
| Decode of variable
| False
| Do of sat_constraint T.t
let rec erase
: type a e. (a, e) Constraint.t -> sat_constraint
= function
| Exist (v, c, s) -> Exist (v, c, erase s)
| Map (c, _) -> erase c
| MapErr (c, _) -> erase c
| Ret _v -> Conj []
| Err _e -> False
| Conj (_, _) as conj ->
let rec peel
: type a e . (a, e) Constraint.t -> sat_constraint list
= function
| Map (c, _) -> peel c
| MapErr (c, _) -> peel c
| Conj (c1, c2) -> peel c1 @ peel c2
| Err _ -> [False]
| Ret _ -> []
| Exist _ as c -> [erase c]
| Eq _ as c -> [erase c]
| Decode _ as c -> [erase c]
| Do _ as c -> [erase c]
in
begin match peel conj with
| [c] -> c
| cases -> Conj cases
end
| Eq (v1, v2) -> Eq (v1, v2)
| Decode v -> Decode v
| Do c -> Do (T.map erase c)
end
+55
View File
@@ -0,0 +1,55 @@
{
open UntypedParser
let keyword_table =
Hashtbl.create 17
let keywords = [
"let", LET;
"in", IN;
"lambda", LAMBDA;
]
let _ =
List.iter
(fun (kwd, tok) -> Hashtbl.add keyword_table kwd tok)
keywords
let new_line lexbuf =
Lexing.new_line lexbuf
}
let identchar = ['a'-'z' 'A'-'Z' '0'-'9' '_']
let lident = ['a'-'z'] identchar*
let blank = [' ' '\t']+
let newline = '\r' | '\n' | "\r\n"
rule read = parse
| eof { EOF }
| newline { new_line lexbuf; read lexbuf }
| blank { read lexbuf }
| lident as id { try Hashtbl.find keyword_table id
with Not_found -> LIDENT id }
| "->" { ARROW }
| '(' { LPAR }
| ')' { RPAR }
| '*' { STAR }
| ',' { COMMA }
| '=' { EQ }
| ":" { COLON }
| '.' { PERIOD }
| "--" { line_comment lexbuf; read lexbuf }
| _ as c
{ failwith
(Printf.sprintf
"Unexpected character during lexing: %c" c) }
and line_comment = parse
| newline
{ new_line lexbuf; () }
| eof
{ failwith "Unterminated OCaml comment: \
no newline at end of file." }
| _
{ line_comment lexbuf }
+105
View File
@@ -0,0 +1,105 @@
%{
open Untyped.Make(Utils.Empty)
%}
%token <string> LIDENT
%token EOF
%token LET "let"
%token IN "in"
%token LAMBDA "lambda"
%token ARROW "->"
%token LPAR "("
%token RPAR ")"
%token STAR "*"
%token COMMA ","
%token EQ "="
%token COLON ":"
%token PERIOD "."
%type<Untyped.Make(Utils.Empty).raw_term> term_eof
%start term_eof
%%
let term_eof :=
| ~ = term ; EOF ;
<>
(***************** TERMS ***************)
let term :=
| ~ = term_abs ; <>
let term_abs :=
| "lambda" ; xs = list (tevar) ; "." ; t = term_abs ;
{ List.fold_right (fun x t -> Abs (x, t)) xs t }
| (x, t1, t2) = letin(tevar) ;
{ Let (x, t1, t2) }
| (xs, t1, t2) = letin(tuple(tevar)) ;
{ LetTuple (xs, t1, t2) }
| ~ = term_app ; <>
let term_app :=
| t1 = term_app ; t2 = term_atom ;
{ App (t1, t2) }
| ~ = term_atom ; <>
let term_atom :=
| x = tevar ;
{ Var x }
| ts = tuple (term) ;
{ Tuple ts }
| "(" ; t = term ; ":" ; ty = typ ; ")" ;
{ Annot (t, ty) }
| "(" ; ~ = term ; ")" ; <>
let tevar :=
| ~ = LIDENT ; <>
let letin (X) :=
| LET ; x = X ; EQ ;
t1 = term ; IN ;
t2 = term_abs ;
{ (x, t1, t2) }
let tuple (X) :=
| "(" ; ")" ;
{ [] }
(* note: the rule below enforces that one-element lists always
end with a trailing comma *)
| "(" ; x = X ; COMMA ; xs = item_sequence(X, COMMA) ; ")";
{ x :: xs }
(* item sequence with optional trailing separator *)
let item_sequence(X, Sep) :=
|
{ [] }
| x = X ;
{ [x] }
| x = X ; () = Sep ; xs = item_sequence(X, Sep) ;
{ x :: xs }
(*************** TYPES ***************)
let typ :=
| ~ = typ_arrow ; <>
let typ_arrow :=
| ty1 = typ_atom ; "->" ; ty2 = typ_arrow ;
{ STLC.Constr (Structure.Arrow (ty1, ty2)) }
| ~ = typ_atom ; <>
let typ_atom :=
| x = tyvar ;
{ STLC.Constr (Structure.Var x) }
| "(" ; tys = separated_list ("*", typ) ; ")" ;
{ STLC.Constr (Structure.Prod tys) }
| "(" ; ~ = typ ; ")" ; <>
let tyvar :=
| ~ = LIDENT ; <>
+51
View File
@@ -0,0 +1,51 @@
module Make(T : Utils.Functor) = struct
open Untyped.Make(T)
let print_term : term -> PPrint.document =
let rec print_top t = print_left_open t
and print_left_open t =
let print_self = print_left_open
and print_next = print_app in
PPrint.group @@ match t with
| Abs (x, t) ->
Printer.lambda
~input:(Var.print x)
~body:(print_self t)
| Let (x, t, u) ->
Printer.let_
~var:(Var.print x)
~def:(print_top t)
~body:(print_self u)
| LetTuple (xs, t, u) ->
Printer.let_
~var:(Printer.tuple Var.print xs)
~def:(print_top t)
~body:(print_self u)
| other -> print_next other
and print_app t =
let print_self = print_app
and print_next = print_atom in
PPrint.group @@ match t with
| App (t, u) ->
Printer.app (print_self t) (print_next u)
| other -> print_next other
and print_atom t =
PPrint.group @@ match t with
| Var x -> Var.print x
| Annot (t, ty) ->
Printer.annot
(print_top t)
(STLCPrinter.print_ty ty)
| Tuple ts ->
Printer.tuple print_top ts
| (App _ | Abs _ | Let _ | LetTuple _) as other ->
PPrint.parens (print_top other)
| Do _p ->
Printer.do_
in print_top
end
+4
View File
@@ -0,0 +1,4 @@
module Make(T : Utils.Functor) : sig
module Untyped := Untyped.Make(T)
val print_term : Untyped.term -> PPrint.document
end
+5
View File
@@ -0,0 +1,5 @@
(ocamllex UntypedLexer)
(menhir
(modules UntypedParser)
(flags --explain))