Files
why3/plugins/python/py_main.ml

486 lines
19 KiB
OCaml

(********************************************************************)
(* *)
(* The Why3 Verification Platform / The Why3 Development Team *)
(* Copyright 2010-2022 -- Inria - CNRS - Paris-Saclay University *)
(* *)
(* This software is distributed under the terms of the GNU Lesser *)
(* General Public License version 2.1, with the special exception *)
(* on linking described in file LICENSE. *)
(* *)
(********************************************************************)
open Why3
open Pmodule
open Py_ast
open Ptree
open Wstdlib
let debug = Debug.register_flag "python"
~desc:"mini-python plugin debug flag"
(* NO! this will be executed at plugin load, thus
disabling the warning for ALL WHY3 USERS even if they don't
use the python front-end
let () = Debug.set_flag Dterm.debug_ignore_unused_var
*)
let mk_id ~loc name =
{ id_str = name; id_ats = []; id_loc = loc }
let id_infix ~loc s = mk_id ~loc (Ident.op_infix s)
let infix ~loc s = Qident (id_infix ~loc s)
let prefix ~loc s = Qident (mk_id ~loc (Ident.op_prefix s))
let get_op ~loc = Qident (mk_id ~loc (Ident.op_get ""))
let set_op ~loc = Qident (mk_id ~loc (Ident.op_set ""))
let mk_expr ~loc d =
{ expr_desc = d; expr_loc = loc }
let mk_pat ~loc d =
{ pat_desc = d; pat_loc = loc }
let mk_unit ~loc =
mk_expr ~loc (Etuple [])
let mk_var ~loc id =
mk_expr ~loc (Eident (Qident id))
let mk_ref ~loc e =
mk_expr ~loc (Eidapp (Qident (mk_id ~loc "ref"), [e]))
let array_set ~loc a i v =
mk_expr ~loc (Eidapp (set_op ~loc, [a; i; v]))
let constant ~loc i =
mk_expr ~loc (Econst (Constant.int_const_of_int i))
let constant_s ~loc s =
let int_lit = Number.(int_literal ILitDec ~neg:false s) in
mk_expr ~loc (Econst (Constant.ConstInt int_lit))
let len ~loc =
Qident (mk_id ~loc "len")
let break_id = "'Break"
let continue_id = "'Continue"
let return_id = "'Return"
let array_make ~loc n v =
mk_expr ~loc (Eidapp (Qdot (Qident (mk_id ~loc "Python"), mk_id ~loc "make"),
[n; v]))
let array_empty ~loc =
mk_expr ~loc (Eidapp (Qdot (Qident (mk_id ~loc "Python"), mk_id ~loc "empty"), [mk_unit ~loc]))
let set_ref id =
{ id with id_ats = ATstr Pmodule.ref_attr :: id.id_ats }
let empty_spec = {
sp_pre = [];
sp_post = [];
sp_xpost = [];
sp_reads = [];
sp_writes = [];
sp_alias = [];
sp_variant = [];
sp_checkrw = false;
sp_diverge = false;
sp_partial = false;
}
type env = {
vars: ident Mstr.t;
}
let empty_env =
{ vars = Mstr.empty }
let is_const (e: Py_ast.expr list) =
match List.nth e 2 with
| {Py_ast.expr_desc=Eint "1";_} -> 1
| {Py_ast.expr_desc=Eunop (Uneg, {Py_ast.expr_desc=Eint "1";_});_} -> -1
| _ -> 0
let add_var env id =
{ vars = Mstr.add id.id_str id env.vars }
let add_param env (id, _) =
add_var env id
let for_vars ~loc x =
let x = x.id_str in
mk_id ~loc (x ^ "'index"), mk_id ~loc (x ^ "'list")
let rec has_stmt p = function
| Dstmt s -> p s || begin match s.stmt_desc with
| Sbreak | Scontinue | Sreturn _ | Sassign _ | Slabel _
| Seval _ | Sset _ | Sblock _ | Sassert _ | Swhile _ -> false
| Sif (_, bl1, bl2) -> has_stmtl p bl1 || has_stmtl p bl2
| Sfor (_, _, _, bl) -> has_stmtl p bl end
| _ -> false
and has_stmtl p bl = List.exists (has_stmt p) bl
let rec expr_has_call id e = match e.Py_ast.expr_desc with
| Enone | Ebool _ | Eint _ | Estring _ | Py_ast.Eident _ -> false
| Emake (e1, e2) | Eget (e1, e2) | Ebinop (_, e1, e2) ->
expr_has_call id e1 || expr_has_call id e2
| Eunop (_, e1) -> expr_has_call id e1
| Edot (e, f, el) -> id.id_str = f.id_str || List.exists (expr_has_call id) (e::el)
| Ecall (f, el) -> id.id_str = f.id_str || List.exists (expr_has_call id) el
| Elist el -> List.exists (expr_has_call id) el
let rec stmt_has_call id s = match s.stmt_desc with
| Sbreak | Scontinue | Slabel _ | Sassert _ -> false
| Sreturn e | Sassign (_, e) | Seval e -> expr_has_call id e
| Sblock s -> block_has_call id s
| Sset (e1, e2, e3) ->
expr_has_call id e1 || expr_has_call id e2 || expr_has_call id e3
| Sif (e, s1, s2) -> expr_has_call id e || block_has_call id s1 || block_has_call id s2
| Sfor (_, e, _, s) | Swhile (e, _, _, s) -> expr_has_call id e || block_has_call id s
and block_has_call id = has_stmtl (stmt_has_call id)
let rec expr env {Py_ast.expr_loc = loc; Py_ast.expr_desc = d } = match d with
| Py_ast.Enone ->
mk_unit ~loc
| Py_ast.Ebool b ->
mk_expr ~loc (if b then Etrue else Efalse)
| Py_ast.Eint s ->
constant_s ~loc s
| Py_ast.Estring _s ->
mk_unit ~loc (*FIXME*)
| Py_ast.Eident id ->
if not (Mstr.mem id.id_str env.vars) then
Loc.errorm ~loc "unbound variable %s" id.id_str;
mk_expr ~loc (Eident (Qident id))
| Py_ast.Ebinop (op, e1, e2) ->
let e1 = expr env e1 in
let e2 = expr env e2 in
mk_expr ~loc (match op with
| Py_ast.Band -> Eand (e1, e2)
| Py_ast.Bor -> Eor (e1, e2)
| Py_ast.Badd -> Eidapp (infix ~loc "+", [e1; e2])
| Py_ast.Bsub -> Eidapp (infix ~loc "-", [e1; e2])
| Py_ast.Bmul -> Eidapp (infix ~loc "*", [e1; e2])
| Py_ast.Bdiv -> Eidapp (infix ~loc "//", [e1; e2])
| Py_ast.Bmod -> Eidapp (infix ~loc "%", [e1; e2])
| Py_ast.Beq -> Einnfix (e1, id_infix ~loc "=", e2)
| Py_ast.Bneq -> Einnfix (e1, id_infix ~loc "<>", e2)
| Py_ast.Blt -> Einnfix (e1, id_infix ~loc "<", e2)
| Py_ast.Ble -> Einnfix (e1, id_infix ~loc "<=", e2)
| Py_ast.Bgt -> Einnfix (e1, id_infix ~loc ">", e2)
| Py_ast.Bge -> Einnfix (e1, id_infix ~loc ">=", e2)
)
| Py_ast.Eunop (Py_ast.Uneg, e) ->
mk_expr ~loc (Eidapp (prefix ~loc "-", [expr env e]))
| Py_ast.Eunop (Py_ast.Unot, e) ->
mk_expr ~loc (Enot (expr env e))
| Py_ast.Edot (e, f, el) ->
let el = List.map (expr env) (e::el) in
let id = Qdot (Qident (mk_id ~loc "Python"), f) in
mk_expr ~loc (Eidapp (id, el))
| Py_ast.Ecall ({id_str="slice"} as id, [e1;e2;e3]) ->
let zero = expr env ({Py_ast.expr_loc = loc; Py_ast.expr_desc = Eint "0"}) in
let e1' = mk_id ~loc "e1'" in
let e1_var = mk_var ~loc e1' in
let len = mk_expr ~loc (Eidapp (Qident (mk_id ~loc "len"), [e1_var])) in
let e2, e3 = match e2, e3 with
| {Py_ast.expr_desc=Py_ast.Enone}, {Py_ast.expr_desc=Py_ast.Enone} -> zero, len
| _ , {Py_ast.expr_desc=Py_ast.Enone} -> expr env e2, len
| {Py_ast.expr_desc=Py_ast.Enone}, _ -> zero, expr env e3
| _ , _ -> expr env e2, expr env e3
in
let id = Qdot (Qident (mk_id ~loc "Python"), id) in
mk_expr ~loc (Elet (e1', false, Expr.RKnone, expr env e1,
mk_expr ~loc (Eidapp (id, [e1_var;e2;e3]))
))
| Py_ast.Ecall ({id_str="range"} as id, els) when List.length els < 4 ->
let zero = {Py_ast.expr_loc = loc; Py_ast.expr_desc = Eint "0"} in
let from_to_step, id = match els with
| [e] -> [zero; e], id
| [e1;e2] -> [e1; e2], id
| [e1;e2;e3] -> [e1; e2; e3], mk_id ~loc "range3"
| _ -> assert false
in
let el = List.map (expr env) from_to_step in
mk_expr ~loc (Eidapp (Qident id, el))
| Py_ast.Ecall ({id_str="print"}, el) ->
let eval res e =
mk_expr ~loc (Elet (mk_id ~loc "_", false, Expr.RKnone, expr env e, res)) in
List.fold_left eval (mk_unit ~loc) el
| Py_ast.Ecall (id, el) ->
let el = if el = [] then [mk_unit ~loc] else List.map (expr env) el in
mk_expr ~loc (Eidapp (Qident id, el))
| Py_ast.Emake (e1, e2) -> (* [e1]*e2 *)
array_make ~loc (expr env e2) (expr env e1)
| Py_ast.Elist [] ->
array_empty ~loc
| Py_ast.Elist el ->
let n = List.length el in
let n = constant ~loc n in
let id = mk_id ~loc "new array" in
mk_expr ~loc (Elet (id, false, Expr.RKnone, array_make ~loc n (constant ~loc 0),
let i = ref (-1) in
let init seq e =
incr i; let i = constant ~loc !i in
let assign = array_set ~loc (mk_var ~loc id) i (expr env e) in
mk_expr ~loc (Esequence (assign, seq)) in
List.fold_left init (mk_var ~loc id) el))
| Py_ast.Eget (e1, e2) ->
mk_expr ~loc (Eidapp (get_op ~loc, [expr env e1; expr env e2]))
let no_params ~loc = [loc, None, false, Some (PTtuple [])]
let mk_for_params exps loc env =
let mk_op1 op ub = mk_expr ~loc (Eidapp (infix ~loc op, [expr env ub; constant ~loc 1])) in
let mk_minus1 ub = mk_op1 "-" ub in
let mk_plus1 ub = mk_op1 "+" ub in
match exps with
| [e1] ->
let zero = {Py_ast.expr_loc = loc; Py_ast.expr_desc = Eint "0"} in
expr env zero, mk_minus1 e1, Expr.To
| [e1;e2] ->
expr env e1, mk_minus1 e2, Expr.To
| [e1;e2;_] ->
begin match is_const exps with
| 1 -> expr env e1, mk_minus1 e2, Expr.To
| -1 -> expr env e1, mk_plus1 e2, Expr.DownTo
| _ -> assert false
end
| _ -> assert false
let rec stmt env ({Py_ast.stmt_loc = loc; Py_ast.stmt_desc = d } as s) =
match d with
| Py_ast.Sblock s ->
block env ~loc s
| Py_ast.Seval e ->
let id = mk_id ~loc "_'" in
mk_expr ~loc (Elet (id, false, Expr.RKnone, expr env e, mk_unit ~loc))
| Py_ast.Sif (e, s1, s2) ->
mk_expr ~loc (Eif (expr env e, block env ~loc s1, block env ~loc s2))
| Py_ast.Sreturn e ->
mk_expr ~loc (Eraise (Qident (mk_id ~loc return_id), Some (expr env e)))
| Py_ast.Sassign (id, e) ->
let e = expr env e in
if Mstr.mem id.id_str env.vars then
let x = let loc = id.id_loc in mk_expr ~loc (Eident (Qident id)) in
mk_expr ~loc (Einfix (x, mk_id ~loc (Ident.op_infix ":="), e))
else
block env ~loc [Dstmt s]
| Py_ast.Sset (e1, e2, e3) ->
array_set ~loc (expr env e1) (expr env e2) (expr env e3)
| Py_ast.Sassert (k, t) ->
mk_expr ~loc (Eassert (k, t))
| Py_ast.Swhile (e, inv, var, s) ->
let id_b = mk_id ~loc break_id in
let id_c = mk_id ~loc continue_id in
let body = block env ~loc s in
let body = mk_expr ~loc (Eoptexn (id_c, Ity.MaskVisible, body)) in
let loop = mk_expr ~loc (Ewhile (expr env e, inv, var, body)) in
mk_expr ~loc (Eoptexn (id_b, Ity.MaskVisible, loop))
| Py_ast.Sbreak ->
mk_expr ~loc (Eraise (Qident (mk_id ~loc break_id), None))
| Py_ast.Scontinue ->
mk_expr ~loc (Eraise (Qident (mk_id ~loc continue_id), None))
| Py_ast.Slabel _ ->
mk_unit ~loc (* ignore lonely marks *)
(* make a special case for
for id in range(e1, [e2, e3])
*)
| Py_ast.Sfor (id, {Py_ast.expr_desc=Ecall ({id_str="range"}, exps)},
inv, body)
when (List.length exps = 3 && (let c = is_const exps in c = -1 || c = 1)) ->
let lb, ub, direction = mk_for_params exps loc env in
let body = block ~loc (add_var env id) body in
let body = mk_expr ~loc (Eoptexn (mk_id ~loc continue_id, Ity.MaskVisible, body)) in
let body = mk_expr ~loc (Elet (set_ref id, false, Expr.RKnone,
mk_ref ~loc (mk_var ~loc id), body)) in
let loop = mk_expr ~loc (Efor (id, lb, direction, ub, inv, body)) in
mk_expr ~loc (Eoptexn (mk_id ~loc break_id, Ity.MaskVisible, loop))
| Py_ast.Sfor (id, {Py_ast.expr_desc=Ecall ({id_str="range"}, exps)},
inv, body)
when (List.length exps < 3) ->
let lb, ub, direction = mk_for_params exps loc env in
let body = block ~loc (add_var env id) body in
let body = mk_expr ~loc (Eoptexn (mk_id ~loc continue_id, Ity.MaskVisible, body)) in
let body = mk_expr ~loc (Elet (set_ref id, false, Expr.RKnone,
mk_ref ~loc (mk_var ~loc id), body)) in
let loop = mk_expr ~loc (Efor (id, lb, direction, ub, inv, body)) in
mk_expr ~loc (Eoptexn (mk_id ~loc break_id, Ity.MaskVisible, loop))
(* otherwise, translate
for id in e:
#@ invariant inv
body
to
let l = e in
for i'index = 0 to len(l)-1 do
invariant { I }
let id = ref l[i'index] in
body
done
*)
| Py_ast.Sfor (id, e, inv, body) ->
let e = expr env e in
let i, l = for_vars ~loc id in
let lb = constant ~loc 0 in
let lenl = mk_expr ~loc (Eidapp (len ~loc, [mk_var ~loc l])) in
let ub = mk_expr ~loc (Eidapp (infix ~loc "-", [lenl; constant ~loc 1])) in
let li = mk_expr ~loc (Eidapp (get_op ~loc, [mk_var ~loc l; mk_var ~loc i])) in
let body = block ~loc (add_var env id) body in
let body = mk_expr ~loc (Eoptexn (mk_id ~loc continue_id, Ity.MaskVisible, body)) in
let body = mk_expr ~loc (Elet (set_ref id, false, Expr.RKnone,
mk_ref ~loc li, body)) in
let loop = mk_expr ~loc (Efor (i, lb, Expr.To, ub, inv, body)) in
let loop = mk_expr ~loc (Elet (l, false, Expr.RKnone, e, loop)) in
mk_expr ~loc (Eoptexn (mk_id ~loc break_id, Ity.MaskVisible, loop))
and block env ~loc = function
| [] ->
mk_unit ~loc
| Dstmt { stmt_loc = loc; stmt_desc = Slabel id } :: sl ->
mk_expr ~loc (Elabel (id, block env ~loc sl))
| Dstmt { Py_ast.stmt_loc = loc; stmt_desc = Py_ast.Sassign (id, e) } :: sl
when not (Mstr.mem id.id_str env.vars) ->
let e = expr env e in (* check e *before* adding id to environment *)
let env = add_var env id in
mk_expr ~loc (Elet (set_ref id, false, Expr.RKnone, mk_ref ~loc e, block env ~loc sl))
| Dstmt ({ Py_ast.stmt_loc = loc } as s) :: sl ->
let s = stmt env s in
if sl = [] then s else mk_expr ~loc (Esequence (s, block env ~loc sl))
| Ddef (id, idl, ty, sp, bl) :: sl ->
(* f(x1,...,xn): body ==>
let f x1 ... xn =
let x1 = ref x1 in ... let xn = ref xn in
try body with Return x -> x *)
let env' = List.fold_left add_param empty_env idl in
let body = block env' ~loc:id.id_loc bl in
let body =
let loc = id.id_loc in
let id = mk_id ~loc return_id in
{ body with expr_desc = Eoptexn (id, Ity.MaskVisible, body) } in
let local bl (id, _) =
let loc = id.id_loc in
let ref = mk_ref ~loc (mk_var ~loc id) in
mk_expr ~loc (Elet (set_ref id, false, Expr.RKnone, ref, bl)) in
let body = List.fold_left local body idl in
let param (id, ty) =
id.id_loc, Some id, false, ty in
let params = if idl = [] then no_params ~loc else List.map param idl in
let s = block env ~loc sl in
let p = mk_pat ~loc Pwild in
let e = if block_has_call id bl then
Erec ([id, false, Expr.RKnone, params, ty, p, Ity.MaskVisible, sp, body], s)
else
let e = Efun (params, ty, p, Ity.MaskVisible, sp, body) in
Elet (id, false, Expr.RKnone, mk_expr ~loc e, s) in
mk_expr ~loc e
| (Py_ast.Dimport _ | Py_ast.Dlogic _) :: sl ->
block env ~loc sl
let logic_param (id, ty) =
id.id_loc, Some id, false, ty
let logic = function
| Py_ast.Dlogic (id, idl, ty, def) ->
let d = { ld_loc = id.id_loc;
ld_ident = id;
ld_params = List.map logic_param idl;
ld_type = ty;
ld_def = def } in
Typing.add_decl id.id_loc (Dlogic [d])
| _ -> ()
let translate ~loc dl =
List.iter logic dl;
let bl = block empty_env ~loc dl in
let p = mk_pat ~loc Pwild in
let fd = Efun (no_params ~loc, None, p, Ity.MaskVisible, empty_spec, bl) in
let main = Dlet (mk_id ~loc "main", false, Expr.RKnone, mk_expr ~loc fd) in
Typing.add_decl loc main
let read_channel env path file c =
let f : Py_ast.file = Py_lexer.parse file c in
Debug.dprintf debug "%s parsed successfully.@." file;
let file = Filename.basename file in
let file = Filename.chop_extension file in
let name = Strings.capitalize file in
Debug.dprintf debug "building module %s.@." name;
Typing.open_file env path;
let loc = Loc.user_position file 0 0 0 0 in
Typing.open_module (mk_id ~loc name);
let use_import (f, m) =
let m = mk_id ~loc m in
let qid = Qdot (Qident (mk_id ~loc f), m) in
let decl = Ptree.Duseimport(loc,false,[(qid,None)]) in
Typing.add_decl loc decl in
List.iter use_import
["int", "Int"; "ref", "Refint"; "python", "Python"];
translate ~loc f;
Typing.close_module loc;
let mm = Typing.close_file () in
if path = [] && Debug.test_flag debug then begin
let add_m _ m modm = Ident.Mid.add m.mod_theory.Theory.th_name m modm in
let print_m _ m = Pmodule.print_module Format.err_formatter m in
Ident.Mid.iter print_m (Mstr.fold add_m mm Ident.Mid.empty)
end;
mm
let () =
Env.register_format mlw_language "python" ["py"] read_channel
~desc:"mini-Python format"
(* Python pretty-printer, to print tasks with a little bit
of Python syntax *)
open Term
open Format
open Pretty
(* python print_binop *)
let print_binop ~asym fmt = function
| Tand when asym -> pp_print_string fmt "&&"
| Tor when asym -> pp_print_string fmt "||"
| Tand -> pp_print_string fmt "and"
| Tor -> pp_print_string fmt "or"
| Timplies -> pp_print_string fmt "->"
| Tiff -> pp_print_string fmt "<->"
(* Register the transformations functions *)
let rec python_ext_printer print_any fmt a =
match a with
| Pp_term (t, pri) ->
begin match t.t_node with
| Tapp (ls, [t1; t2]) when ls_equal ls ps_equ ->
(* == *)
fprintf fmt (protect_on (pri > 0) "@[%a == %a@]")
(python_ext_printer print_any) (Pp_term (t1, 0))
(python_ext_printer print_any) (Pp_term (t2, 0))
| Tnot {t_node = Tapp (ls, [t1; t2]) } when ls_equal ls ps_equ ->
(* != *)
fprintf fmt (protect_on (pri > 0) "@[%a != %a@]")
(python_ext_printer print_any) (Pp_term (t1, 0))
(python_ext_printer print_any) (Pp_term (t2, 0))
| Tbinop (b, f1, f2) ->
(* and, or *)
let asym = Ident.Sattr.mem asym_split f1.t_attrs in
let p = prio_binop b in
fprintf fmt (protect_on (pri > p) "@[%a %a@ %a@]")
(python_ext_printer print_any) (Pp_term (f1, (p + 1)))
(print_binop ~asym) b
(python_ext_printer print_any) (Pp_term (f2, p))
| _ -> print_any fmt a
end
| _ -> print_any fmt a
let () = Itp_server.add_registered_lang "python" (fun _ -> python_ext_printer)
let () = Args_wrapper.set_argument_parsing_functions "python"
~parse_term:(fun _ lb -> Py_lexer.parse_term lb)
~parse_term_list:(fun _ lb -> Py_lexer.parse_term_list lb)
~parse_list_ident:(fun lb -> Py_lexer.parse_list_ident lb)
(* TODO for qualids, add a similar funciton *)
~parse_qualid:(fun lb -> Lexer.parse_qualid lb)
~parse_list_qualid:(fun lb -> Lexer.parse_list_qualid lb)