sidekick/src/smtlib/Ast.ml

495 lines
12 KiB
OCaml

(* This file is free software. See file "license" for more details. *)
(** {1 Preprocessing AST} *)
open Sidekick_base_term
type 'a or_error = ('a, string) CCResult.t
(** {2 Types} *)
module Var = struct
type 'ty t = {
id: ID.t;
ty: 'ty;
}
let make id ty = {id;ty}
let makef ~ty fmt =
CCFormat.ksprintf fmt ~f:(fun s -> make (ID.make s) ty)
let copy {id;ty} = {ty; id=ID.copy id}
let id v = v.id
let ty v = v.ty
let equal a b = ID.equal a.id b.id
let compare a b = ID.compare a.id b.id
let pp out v = ID.pp out v.id
end
module Ty = struct
type t =
| Prop
| App of ID.t * t list
| Arrow of t * t
let prop = Prop
let app id l = App (id,l)
let const id = app id []
let arrow a b = Arrow (a,b)
let arrow_l = List.fold_right arrow
let int = const ID.B.int
let rat = const ID.B.rat
let to_int_ = function
| Prop -> 0
| App _ -> 1
| Arrow _ -> 2
let (<?>) = CCOrd.(<?>)
let rec compare a b = match a, b with
| Prop, Prop -> 0
| App (a,la), App (b,lb) ->
CCOrd.(ID.compare a b <?> (list compare, la, lb))
| Arrow (a1,a2), Arrow (b1,b2) ->
compare a1 b1 <?> (compare, a2,b2)
| Prop, _
| App _, _
| Arrow _, _ -> CCInt.compare (to_int_ a) (to_int_ b)
let equal a b = compare a b = 0
let hash _ = 0 (* TODO *)
let unfold ty =
let rec aux acc ty = match ty with
| Arrow (a,b) -> aux (a::acc) b
| _ -> List.rev acc, ty
in
aux [] ty
let rec pp out = function
| Prop -> Fmt.string out "prop"
| App (id,[]) -> ID.pp out id
| App (id,l) -> Fmt.fprintf out "(@[%a@ %a@])" ID.pp id (Util.pp_list pp) l
| Arrow _ as ty ->
let args, ret = unfold ty in
Fmt.fprintf out "(@[-> %a@ %a@])"
(Util.pp_list ~sep:" " pp) args pp ret
(** {2 Datatypes} *)
type data = {
data_id: ID.t;
data_cstors: t ID.Map.t;
}
module Map = CCMap.Make(struct
type _t = t
type t = _t
let compare = compare
end)
let ill_typed fmt =
Error.errorf ("ill-typed: " ^^ fmt)
end
type var = Ty.t Var.t
type op =
| And
| Or
| Imply
| Eq
| Distinct
type arith_op =
| Leq
| Lt
| Geq
| Gt
| Add
| Minus
| Mult
| Div
type binder =
| Fun
| Forall
| Exists
| Mu
type term = {
term: term_cell;
ty: Ty.t;
}
and term_cell =
| Var of var
| Const of ID.t
| Num_z of Z.t
| Num_q of Q.t
| App of term * term list
| If of term * term * term
| Match of term * (var list * term) ID.Map.t
| Select of select * term
| Bind of binder * var * term
| Arith of arith_op * term list
| Let of (var * term) list * term
| Not of term
| Op of op * term list
| Asserting of {t: term; guard: term}
| Undefined_value
| Bool of bool
and select = {
select_name: ID.t lazy_t;
select_cstor: ID.t;
select_i: int;
}
type definition = ID.t * Ty.t * term
type statement =
| SetLogic of string
| SetOption of string list
| SetInfo of string list
| Data of Ty.data list
| TyDecl of ID.t * int (* new atomic cstor *)
| Decl of ID.t * Ty.t
| Define of definition list
| Assert of term
| Assert_bool of int list
| Goal of var list * term
| CheckSat
| Exit
(** {2 Helpers} *)
let is_true = function {term=Bool true;_} -> true | _ -> false
let is_false = function {term=Bool false;_} -> true | _ -> false
let unfold_binder b t =
let rec aux acc t = match t.term with
| Bind (b', v, t') when b=b' -> aux (v::acc) t'
| _ -> List.rev acc, t
in
aux [] t
let unfold_fun = unfold_binder Fun
let pp_binder out = function
| Forall -> Fmt.string out "forall"
| Exists -> Fmt.string out "exists"
| Fun -> Fmt.string out "lambda"
| Mu -> Fmt.string out "mu"
let pp_op out = function
| And -> Fmt.string out "and"
| Or -> Fmt.string out "or"
| Imply -> Fmt.string out "=>"
| Eq -> Fmt.string out "="
| Distinct -> Fmt.string out "distinct"
let pp_arith out = function
| Leq -> Fmt.string out "<="
| Lt -> Fmt.string out "<"
| Geq -> Fmt.string out ">="
| Gt -> Fmt.string out ">"
| Add -> Fmt.string out "+"
| Minus -> Fmt.string out "-"
| Mult -> Fmt.string out "*"
| Div -> Fmt.string out "/"
let pp_term =
let rec pp out t = match t.term with
| Var v -> Var.pp out v
| Const id -> ID.pp out id
| App (f, l) -> Fmt.fprintf out "(@[<hv1>%a@ %a@])" pp f (Util.pp_list pp) l
| If (a,b,c) -> Fmt.fprintf out "(@[<hv>ite@ %a@ %a@ %a@])" pp a pp b pp c
| Match (u, m) ->
let pp_case out (id,(vars,rhs)) =
if vars=[] then Fmt.fprintf out "(@[<2>case %a@ %a@])" ID.pp id pp rhs
else Fmt.fprintf out "(@[<2>case (@[%a@ %a@])@ %a@])"
ID.pp id (Util.pp_list Var.pp) vars pp rhs
in
Fmt.fprintf out "(@[<hv2>match %a@ %a@])"
pp u (Util.pp_list pp_case) (ID.Map.to_list m)
| Select (s, t) ->
Fmt.fprintf out "(@[select_%a_%d@ %a@])"
ID.pp s.select_cstor s.select_i pp t
| Bool b -> Fmt.fprintf out "%B" b
| Not t -> Fmt.fprintf out "(@[<1>not@ %a@])" pp t
| Op (o,l) -> Fmt.fprintf out "(@[<hv1>%a@ %a@])" pp_op o (Util.pp_list pp) l
| Bind (b,v,u) ->
Fmt.fprintf out "(@[<1>%a ((@[%a@ %a@]))@ %a@])"
pp_binder b Var.pp v Ty.pp (Var.ty v) pp u
| Let (vbs,u) ->
Fmt.fprintf out "(@[<1>let (@[%a@])@ %a@])" pp_vbs vbs pp u
| Num_z z -> Z.pp_print out z
| Num_q z -> Q.pp_print out z
| Arith (op, l) ->
Fmt.fprintf out "(@[<hv>%a@ %a@])" pp_arith op (Util.pp_list pp) l
| Undefined_value -> Fmt.string out "<undefined>"
| Asserting {t;guard} ->
Fmt.fprintf out "(@[asserting@ %a@ %a@])" pp t pp guard
and pp_vbs out l =
let pp_vb out (v,t) = Fmt.fprintf out "(@[%a@ %a@])" Var.pp v pp t in
Util.pp_list pp_vb out l
in pp
let pp_ty = Ty.pp
(** {2 Constructors} *)
let term_view t = t.term
let rec app_ty_ ty l : Ty.t = match ty, l with
| _, [] -> ty
| Ty.Arrow (ty_a,ty_rest), a::tail ->
if Ty.equal ty_a a.ty
then app_ty_ ty_rest tail
else Ty.ill_typed "expected `@[%a@]`,@ got `@[%a : %a@]`"
Ty.pp ty_a pp_term a Ty.pp a.ty
| (Ty.Prop | Ty.App _), a::_ ->
Ty.ill_typed "cannot apply ty `@[%a@]`@ to `@[%a@]`" Ty.pp ty pp_term a
let mk_ term ty = {term; ty}
let ty t = t.ty
let true_ = mk_ (Bool true) Ty.prop
let false_ = mk_ (Bool false) Ty.prop
let undefined_value ty = mk_ Undefined_value ty
let asserting t g =
if not (Ty.equal Ty.prop g.ty) then (
Ty.ill_typed "asserting: test must have type prop, not `@[%a@]`" Ty.pp g.ty;
);
mk_ (Asserting {t;guard=g}) t.ty
let var v = mk_ (Var v) (Var.ty v)
let const id ty = mk_ (Const id) ty
let app f l = match f.term, l with
| _, [] -> f
| App (f1, l1), _ ->
let ty = app_ty_ f.ty l in
mk_ (App (f1, l1 @ l)) ty
| _ ->
let ty = app_ty_ f.ty l in
mk_ (App (f, l)) ty
let app_a f a = app f (Array.to_list a)
let if_ a b c =
if a.ty <> Ty.Prop
then Ty.ill_typed "if: test must have type prop, not `@[%a@]`" Ty.pp a.ty;
if not (Ty.equal b.ty c.ty)
then Ty.ill_typed
"if: both branches must have same type,@ not `@[%a@]` and `@[%a@]`"
Ty.pp b.ty Ty.pp c.ty;
mk_ (If (a,b,c)) b.ty
let match_ t m =
let c1, (_, rhs1) = ID.Map.choose m in
ID.Map.iter
(fun c (_, rhs) ->
if not (Ty.equal rhs1.ty rhs.ty)
then Ty.ill_typed
"match: cases %a and %a disagree on return type,@ \
between %a and %a"
ID.pp c1 ID.pp c Ty.pp rhs1.ty Ty.pp rhs.ty)
m;
mk_ (Match (t,m)) rhs1.ty
let let_l vbs t = match vbs with
| [] -> t
| _::_ ->
List.iter
(fun (v,t) ->
if not (Ty.equal (Var.ty v) t.ty) then (
Ty.ill_typed
"let: variable %a : @[%a@]@ and bounded term : %a@ should have same type"
Var.pp v Ty.pp (Var.ty v) Ty.pp t.ty;
);)
vbs;
mk_ (Let (vbs,t)) t.ty
let let_ v t u = let_l [v,t] u
let bind ~ty b v t = mk_ (Bind(b,v,t)) ty
let select ~ty (s:select) (t:term) = mk_ (Select (s,t)) ty
let fun_ v t =
let ty = Ty.arrow (Var.ty v) t.ty in
mk_ (Bind (Fun,v,t)) ty
let quant_ q v t =
if not (Ty.equal t.ty Ty.prop) then (
Ty.ill_typed
"quantifier: bounded term : %a@ should have type prop"
Ty.pp t.ty;
);
let ty = Ty.prop in
mk_ (q v t) ty
let forall = quant_ (fun v t -> Bind (Forall,v,t))
let exists = quant_ (fun v t -> Bind (Exists,v,t))
let mu v t =
if not (Ty.equal (Var.ty v) t.ty)
then Ty.ill_typed "mu-term: var has type %a,@ body %a"
Ty.pp (Var.ty v) Ty.pp t.ty;
let ty = Ty.arrow (Var.ty v) t.ty in
mk_ (Bind (Fun,v,t)) ty
let fun_l = List.fold_right fun_
let fun_a = Array.fold_right fun_
let forall_l = List.fold_right forall
let exists_l = List.fold_right exists
let eq a b =
if not (Ty.equal a.ty b.ty)
then Ty.ill_typed "eq: `@[%a@]` and `@[%a@]` do not have the same type"
pp_term a pp_term b;
mk_ (Op (Eq,[a;b])) Ty.prop
let check_prop_ t =
if not (Ty.equal t.ty Ty.prop) then (
Ty.ill_typed "expected prop, got `@[%a : %a@]`" pp_term t Ty.pp t.ty
)
let op op l = mk_ (Op (op, l)) Ty.prop
let binop_prop o a b =
check_prop_ a; check_prop_ b;
op o [a;b]
let and_ = binop_prop And
let or_ = binop_prop Or
let imply = binop_prop Imply
let and_l = function
| [] -> true_
| [f] -> f
| l -> op And l
let or_l = function
| [] -> false_
| [f] -> f
| l -> op Or l
let not_ t =
check_prop_ t;
mk_ (Not t) Ty.prop
let arith ty op l = mk_ (Arith (op,l)) ty
let num_q ty z = mk_ (Num_q z) ty
let num_z ty z = mk_ (Num_z z) ty
let parse_num ~where (s:string) : [`Q of Q.t | `Z of Z.t] =
let fail() =
Error.errorf "%sexpected number, got `%s`" (Lazy.force where) s
in
begin match Z.of_string s with
| n -> `Z n
| exception _ ->
begin match Q.of_string s with
| n -> `Q n
| exception _ ->
if String.contains s '.' then (
let p1, p2 = CCString.Split.left_exn ~by:"." s in
let n1, n2 =
try Z.of_string p1, Z.of_string p2
with _ -> fail()
in
let factor_10 = Z.pow (Z.of_int 10) (String.length p2) in
(* [(p1·10^{length p2}+p2) / 10^{length p2}] *)
let n =
Q.div
(Q.of_bigint (Z.add n2 (Z.mul n1 factor_10)))
(Q.of_bigint factor_10)
in
`Q n
) else fail()
end
end
let num_str ty s =
begin match parse_num ~where:(Lazy.from_val "") s with
| `Q x -> num_q ty x
| `Z x -> num_z ty x
end
(** {2 More IO} *)
let pp_statement out = function
| SetLogic s -> Fmt.fprintf out "(set-logic %s)" s
| SetOption l -> Fmt.fprintf out "(@[set-logic@ %a@])" (Util.pp_list Fmt.string) l
| SetInfo l -> Fmt.fprintf out "(@[set-info@ %a@])" (Util.pp_list Fmt.string) l
| CheckSat -> Fmt.string out "(check-sat)"
| TyDecl (s,n) -> Fmt.fprintf out "(@[declare-sort@ %a %d@])" ID.pp s n
| Decl (id,ty) ->
let args, ret = Ty.unfold ty in
Fmt.fprintf out "(@[<1>declare-fun@ %a (@[%a@])@ %a@])"
ID.pp id (Util.pp_list Ty.pp) args Ty.pp ret
| Assert t -> Fmt.fprintf out "(@[assert@ %a@])" pp_term t
| Assert_bool l -> Fmt.fprintf out "(@[assert-bool@ %a@])" (Util.pp_list Fmt.int) l
| Goal (vars,g) ->
Fmt.fprintf out "(@[assert-not@ %a@])" pp_term (forall_l vars (not_ g))
| Exit -> Fmt.string out "(exit)"
| Data _ -> assert false (* TODO *)
| Define _ -> assert false (* TODO *)
(** {2 Environment} *)
type env_entry =
| E_uninterpreted_ty
| E_uninterpreted_cst (* domain element *)
| E_const of Ty.t
| E_data of Ty.t ID.Map.t (* list of cstors *)
| E_cstor of Ty.t (* datatype it belongs to *)
| E_defined of Ty.t * term (* if defined *)
type env = {
defs: env_entry ID.Map.t;
}
(** Environment with definitions and goals *)
let env_empty = {
defs=ID.Map.empty;
}
let add_def id def env = { defs=ID.Map.add id def env.defs}
let env_add_statement env st =
match st with
| Data l ->
List.fold_left
(fun env {Ty.data_id; data_cstors} ->
let map = add_def data_id (E_data data_cstors) env in
ID.Map.fold
(fun c_id c_ty map -> add_def c_id (E_cstor c_ty) map)
data_cstors map)
env l
| TyDecl (id,_) -> add_def id E_uninterpreted_ty env
| Decl (id,ty) -> add_def id (E_const ty) env
| Define l ->
List.fold_left
(fun map (id,ty,def) -> add_def id (E_defined (ty,def)) map)
env l
| Goal _ | Assert _ | Assert_bool _ | CheckSat | Exit
| SetLogic _ | SetOption _ | SetInfo _
-> env
let env_of_statements seq =
Iter.fold env_add_statement env_empty seq
let env_find_def env id =
try Some (ID.Map.find id env.defs)
with Not_found -> None
let env_add_def env id def = add_def id def env