2403 lines
80 KiB
OCaml
2403 lines
80 KiB
OCaml
[@@@ocaml.warning "-32"]
|
|
|
|
module F = Fmt
|
|
open Format
|
|
open Misc
|
|
open Parsetree
|
|
open Typedtree
|
|
open Types
|
|
open Trace
|
|
open Cmo_format
|
|
open Outcometree
|
|
open Ast_helper
|
|
open Lambda
|
|
open Translcore
|
|
open Translclass
|
|
open Translobj
|
|
open Debuginfo.Scoped_location
|
|
open Path
|
|
module String = Misc.Stdlib.String
|
|
|
|
exception PPerror
|
|
|
|
type directive_fun =
|
|
| Directive_none of (unit -> unit)
|
|
| Directive_string of (string -> unit)
|
|
| Directive_int of (int -> unit)
|
|
| Directive_ident of (Longident.t -> unit)
|
|
| Directive_bool of (bool -> unit)
|
|
|
|
type directive_info = { section : string; doc : string }
|
|
|
|
(* Phase buffer that stores the last toplevel phrase (see [Location.input_phrase_buffer]). *)
|
|
let phrase_buffer = Buffer.create 1024
|
|
|
|
(* The table of toplevel value bindings and its accessors *)
|
|
let toplevel_value_bindings : Obj.t String.Map.t ref = ref String.Map.empty
|
|
|
|
let getvalue name =
|
|
try String.Map.find name !toplevel_value_bindings
|
|
with Not_found -> fatal_error (name ^ " unbound at toplevel")
|
|
|
|
let setvalue name v =
|
|
toplevel_value_bindings := String.Map.add name v !toplevel_value_bindings
|
|
|
|
let print_toplevel_value_bindings ppf =
|
|
String.Map.iter
|
|
(fun k _ -> F.pf ppf "toplevel_value_bindings: %s\n" k)
|
|
!toplevel_value_bindings
|
|
|
|
(* copied in lots of ocaml/lambda/translmod.ml *)
|
|
|
|
type unsafe_component =
|
|
| Unsafe_module_binding
|
|
| Unsafe_functor
|
|
| Unsafe_non_function
|
|
| Unsafe_typext
|
|
|
|
type unsafe_info =
|
|
| Unsafe of { reason : unsafe_component; loc : Location.t; subid : Ident.t }
|
|
| Unnamed
|
|
|
|
type error =
|
|
| Circular_dependency of (Ident.t * unsafe_info) list
|
|
| Conflicting_inline_attributes
|
|
|
|
exception Error of Location.t * error
|
|
|
|
let cons_opt x_opt xs = match x_opt with None -> xs | Some x -> x :: xs
|
|
|
|
(* Keep track of the root path (from the root of the namespace to the
|
|
currently compiled module expression). Useful for naming extensions. *)
|
|
|
|
let functor_path path param =
|
|
match path with None -> None | Some p -> Some (Papply (p, Pident param))
|
|
|
|
let field_path path field =
|
|
match path with None -> None | Some p -> Some (Pdot (p, Ident.name field))
|
|
|
|
(* Compile type extensions *)
|
|
|
|
let transl_type_extension ~scopes env rootpath tyext body =
|
|
List.fold_right
|
|
(fun ext body ->
|
|
let lam =
|
|
transl_extension_constructor ~scopes env
|
|
(field_path rootpath ext.ext_id)
|
|
ext
|
|
in
|
|
Llet (Strict, Pgenval, ext.ext_id, lam, body))
|
|
tyext.tyext_constructors body
|
|
|
|
(* Compile a coercion *)
|
|
|
|
let rec apply_coercion loc strict restr arg =
|
|
match restr with
|
|
| Tcoerce_none -> arg
|
|
| Tcoerce_structure (pos_cc_list, id_pos_list) ->
|
|
name_lambda strict arg (fun id ->
|
|
let get_field pos =
|
|
if pos < 0 then lambda_unit else Lprim (Pfield pos, [ Lvar id ], loc)
|
|
in
|
|
let lam =
|
|
Lprim
|
|
( Pmakeblock (0, Immutable, None),
|
|
List.map (apply_coercion_field loc get_field) pos_cc_list,
|
|
loc )
|
|
in
|
|
wrap_id_pos_list loc id_pos_list get_field lam)
|
|
| Tcoerce_functor (cc_arg, cc_res) ->
|
|
let param = Ident.create_local "funarg" in
|
|
let carg = apply_coercion loc Alias cc_arg (Lvar param) in
|
|
apply_coercion_result loc strict arg [ (param, Pgenval) ] [ carg ] cc_res
|
|
| Tcoerce_primitive { pc_loc = _; pc_desc; pc_env; pc_type } ->
|
|
Translprim.transl_primitive loc pc_desc pc_env pc_type None
|
|
| Tcoerce_alias (env, path, cc) ->
|
|
let lam = transl_module_path loc env path in
|
|
name_lambda strict arg (fun _ -> apply_coercion loc Alias cc lam)
|
|
|
|
and apply_coercion_field loc get_field (pos, cc) =
|
|
apply_coercion loc Alias cc (get_field pos)
|
|
|
|
and apply_coercion_result loc strict funct params args cc_res =
|
|
match cc_res with
|
|
| Tcoerce_functor (cc_arg, cc_res) ->
|
|
let param = Ident.create_local "funarg" in
|
|
let arg = apply_coercion loc Alias cc_arg (Lvar param) in
|
|
apply_coercion_result loc strict funct
|
|
((param, Pgenval) :: params)
|
|
(arg :: args) cc_res
|
|
| _ ->
|
|
name_lambda strict funct (fun id ->
|
|
Lfunction
|
|
{
|
|
kind = Curried;
|
|
params = List.rev params;
|
|
return = Pgenval;
|
|
attr =
|
|
{
|
|
default_function_attribute with
|
|
is_a_functor = true;
|
|
stub = true;
|
|
};
|
|
loc;
|
|
body =
|
|
apply_coercion loc Strict cc_res
|
|
(Lapply
|
|
{
|
|
ap_loc = loc;
|
|
ap_func = Lvar id;
|
|
ap_args = List.rev args;
|
|
ap_tailcall = Default_tailcall;
|
|
ap_inlined = Default_inline;
|
|
ap_specialised = Default_specialise;
|
|
});
|
|
})
|
|
|
|
and wrap_id_pos_list loc id_pos_list get_field lam =
|
|
let fv = free_variables lam in
|
|
(*Format.eprintf "%a@." Printlambda.lambda lam;
|
|
Ident.Set.iter (fun id -> Format.eprintf "%a " Ident.print id) fv;
|
|
Format.eprintf "@.";*)
|
|
let lam, s =
|
|
List.fold_left
|
|
(fun (lam, s) (id', pos, c) ->
|
|
if Ident.Set.mem id' fv then
|
|
let id'' = Ident.create_local (Ident.name id') in
|
|
( Llet
|
|
( Alias,
|
|
Pgenval,
|
|
id'',
|
|
apply_coercion loc Alias c (get_field pos),
|
|
lam ),
|
|
Ident.Map.add id' id'' s )
|
|
else (lam, s))
|
|
(lam, Ident.Map.empty) id_pos_list
|
|
in
|
|
if s == Ident.Map.empty then lam else Lambda.rename s lam
|
|
|
|
(* Compose two coercions
|
|
apply_coercion c1 (apply_coercion c2 e) behaves like
|
|
apply_coercion (compose_coercions c1 c2) e. *)
|
|
|
|
let rec compose_coercions c1 c2 =
|
|
match (c1, c2) with
|
|
| Tcoerce_none, c2 -> c2
|
|
| c1, Tcoerce_none -> c1
|
|
| Tcoerce_structure (pc1, ids1), Tcoerce_structure (pc2, ids2) ->
|
|
let v2 = Array.of_list pc2 in
|
|
let ids1 =
|
|
List.map
|
|
(fun (id, pos1, c1) ->
|
|
let pos2, c2 = v2.(pos1) in
|
|
(id, pos2, compose_coercions c1 c2))
|
|
ids1
|
|
in
|
|
Tcoerce_structure
|
|
( List.map
|
|
(fun pc ->
|
|
match pc with
|
|
| _, (Tcoerce_primitive _ | Tcoerce_alias _) ->
|
|
(* These cases do not take an argument (the position is -1),
|
|
so they do not need adjusting. *)
|
|
pc
|
|
| p1, c1 ->
|
|
let p2, c2 = v2.(p1) in
|
|
(p2, compose_coercions c1 c2))
|
|
pc1,
|
|
ids1 @ ids2 )
|
|
| Tcoerce_functor (arg1, res1), Tcoerce_functor (arg2, res2) ->
|
|
Tcoerce_functor (compose_coercions arg2 arg1, compose_coercions res1 res2)
|
|
| c1, Tcoerce_alias (env, path, c2) ->
|
|
Tcoerce_alias (env, path, compose_coercions c1 c2)
|
|
| _, _ -> fatal_error "Translmod.compose_coercions"
|
|
|
|
(*
|
|
let apply_coercion a b c =
|
|
Format.eprintf "@[<2>apply_coercion@ %a@]@." Includemod.print_coercion b;
|
|
apply_coercion a b c
|
|
|
|
let compose_coercions c1 c2 =
|
|
let c3 = compose_coercions c1 c2 in
|
|
let open Includemod in
|
|
Format.eprintf "@[<2>compose_coercions@ (%a)@ (%a) =@ %a@]@."
|
|
print_coercion c1 print_coercion c2 print_coercion c3;
|
|
c3
|
|
*)
|
|
|
|
(* Record the primitive declarations occurring in the module compiled *)
|
|
|
|
let primitive_declarations = ref ([] : Primitive.description list)
|
|
|
|
let record_primitive = function
|
|
| { val_kind = Val_prim p; val_loc; _ } ->
|
|
Translprim.check_primitive_arity val_loc p;
|
|
primitive_declarations := p :: !primitive_declarations
|
|
| _ -> ()
|
|
|
|
(* Utilities for compiling "module rec" definitions *)
|
|
|
|
let mod_prim = Lambda.transl_prim "CamlinternalMod"
|
|
|
|
let undefined_location loc =
|
|
let fname, line, char = Location.get_pos_info loc.Location.loc_start in
|
|
Lconst
|
|
(Const_block
|
|
( 0,
|
|
[
|
|
Const_base (Const_string (fname, loc, None));
|
|
const_int line;
|
|
const_int char;
|
|
] ))
|
|
|
|
exception Initialization_failure of unsafe_info
|
|
|
|
let init_shape id modl =
|
|
let rec init_shape_mod subid loc env mty =
|
|
match Mtype.scrape env mty with
|
|
| Mty_ident _ | Mty_alias _ ->
|
|
raise
|
|
(Initialization_failure
|
|
(Unsafe { reason = Unsafe_module_binding; loc; subid }))
|
|
| Mty_signature sg ->
|
|
Const_block (0, [ Const_block (0, init_shape_struct env sg) ])
|
|
| Mty_functor _ ->
|
|
(* can we do better? *)
|
|
raise
|
|
(Initialization_failure
|
|
(Unsafe { reason = Unsafe_functor; loc; subid }))
|
|
and init_shape_struct env sg =
|
|
match sg with
|
|
| [] -> []
|
|
| Sig_value (subid, { val_kind = Val_reg; val_type = ty; val_loc = loc; _ }, _)
|
|
:: rem ->
|
|
let init_v =
|
|
match Ctype.expand_head env ty with
|
|
| { desc = Tarrow (_, _, _, _); _} ->
|
|
const_int 0 (* camlinternalMod.Function *)
|
|
| { desc = Tconstr (p, _, _); _} when Path.same p Predef.path_lazy_t ->
|
|
const_int 1 (* camlinternalMod.Lazy *)
|
|
| _ ->
|
|
let not_a_function =
|
|
Unsafe { reason = Unsafe_non_function; loc; subid }
|
|
in
|
|
raise (Initialization_failure not_a_function)
|
|
in
|
|
init_v :: init_shape_struct env rem
|
|
| Sig_value (_, { val_kind = Val_prim _; _}, _) :: rem ->
|
|
init_shape_struct env rem
|
|
| Sig_value _ :: _rem -> assert false
|
|
| Sig_type (id, tdecl, _, _) :: rem ->
|
|
init_shape_struct (Env.add_type ~check:false id tdecl env) rem
|
|
| Sig_typext (subid, { ext_loc = loc; _}, _, _) :: _ ->
|
|
raise
|
|
(Initialization_failure
|
|
(Unsafe { reason = Unsafe_typext; loc; subid }))
|
|
| Sig_module (id, Mp_present, md, _, _) :: rem ->
|
|
init_shape_mod id md.md_loc env md.md_type
|
|
:: init_shape_struct
|
|
(Env.add_module_declaration ~check:false id Mp_present md env)
|
|
rem
|
|
| Sig_module (id, Mp_absent, md, _, _) :: rem ->
|
|
init_shape_struct
|
|
(Env.add_module_declaration ~check:false id Mp_absent md env)
|
|
rem
|
|
| Sig_modtype (id, minfo, _) :: rem ->
|
|
init_shape_struct (Env.add_modtype id minfo env) rem
|
|
| Sig_class _ :: rem ->
|
|
const_int 2 (* camlinternalMod.Class *) :: init_shape_struct env rem
|
|
| Sig_class_type _ :: rem -> init_shape_struct env rem
|
|
in
|
|
try
|
|
Ok
|
|
( undefined_location modl.mod_loc,
|
|
Lconst (init_shape_mod id modl.mod_loc modl.mod_env modl.mod_type) )
|
|
with Initialization_failure reason -> Result.Error reason
|
|
|
|
(* Reorder bindings to honor dependencies. *)
|
|
|
|
type binding_status =
|
|
| Undefined
|
|
| Inprogress of int option (** parent node *)
|
|
| Defined
|
|
|
|
type id_or_ignore_loc = Id of Ident.t | Ignore_loc of Lambda.scoped_location
|
|
|
|
let extract_unsafe_cycle id status init cycle_start =
|
|
let info i =
|
|
match init.(i) with
|
|
| Result.Error r -> (
|
|
match id.(i) with
|
|
| Id id -> (id, r)
|
|
| Ignore_loc _ ->
|
|
assert false (* Can't refer to something without a name. *))
|
|
| Ok _ -> assert false
|
|
in
|
|
let rec collect stop l i =
|
|
match status.(i) with
|
|
| Inprogress None | Undefined | Defined -> assert false
|
|
| Inprogress (Some i) when i = stop -> info i :: l
|
|
| Inprogress (Some i) -> collect stop (info i :: l) i
|
|
in
|
|
collect cycle_start [] cycle_start
|
|
|
|
let reorder_rec_bindings bindings =
|
|
let id = Array.of_list (List.map (fun (id, _, _, _) -> id) bindings)
|
|
and loc = Array.of_list (List.map (fun (_, loc, _, _) -> loc) bindings)
|
|
and init = Array.of_list (List.map (fun (_, _, init, _) -> init) bindings)
|
|
and rhs = Array.of_list (List.map (fun (_, _, _, rhs) -> rhs) bindings) in
|
|
let fv = Array.map Lambda.free_variables rhs in
|
|
let num_bindings = Array.length id in
|
|
let status = Array.make num_bindings Undefined in
|
|
let res = ref [] in
|
|
let is_unsafe i =
|
|
match init.(i) with Ok _ -> false | Result.Error _ -> true
|
|
in
|
|
let init_res i =
|
|
match init.(i) with Result.Error _ -> None | Ok (a, b) -> Some (a, b)
|
|
in
|
|
let rec emit_binding parent i =
|
|
match status.(i) with
|
|
| Defined -> ()
|
|
| Inprogress _ ->
|
|
status.(i) <- Inprogress parent;
|
|
let cycle = extract_unsafe_cycle id status init i in
|
|
raise (Error (loc.(i), Circular_dependency cycle))
|
|
| Undefined ->
|
|
if is_unsafe i then (
|
|
status.(i) <- Inprogress parent;
|
|
for j = 0 to num_bindings - 1 do
|
|
match id.(j) with
|
|
| Id id when Ident.Set.mem id fv.(i) -> emit_binding (Some i) j
|
|
| _ -> ()
|
|
done);
|
|
res := (id.(i), init_res i, rhs.(i)) :: !res;
|
|
status.(i) <- Defined
|
|
in
|
|
for i = 0 to num_bindings - 1 do
|
|
match status.(i) with
|
|
| Undefined -> emit_binding None i
|
|
| Inprogress _ -> assert false
|
|
| Defined -> ()
|
|
done;
|
|
List.rev !res
|
|
|
|
(* Generate lambda-code for a reordered list of bindings *)
|
|
|
|
let eval_rec_bindings bindings cont =
|
|
let rec bind_inits = function
|
|
| [] -> bind_strict bindings
|
|
| (Ignore_loc _, _, _) :: rem | (_, None, _) :: rem -> bind_inits rem
|
|
| (Id id, Some (loc, shape), _rhs) :: rem ->
|
|
Llet
|
|
( Strict,
|
|
Pgenval,
|
|
id,
|
|
Lapply
|
|
{
|
|
ap_loc = Loc_unknown;
|
|
ap_func = mod_prim "init_mod";
|
|
ap_args = [ loc; shape ];
|
|
ap_tailcall = Default_tailcall;
|
|
ap_inlined = Default_inline;
|
|
ap_specialised = Default_specialise;
|
|
},
|
|
bind_inits rem )
|
|
and bind_strict = function
|
|
| [] -> patch_forwards bindings
|
|
| (Ignore_loc loc, None, rhs) :: rem ->
|
|
Lsequence (Lprim (Pignore, [ rhs ], loc), bind_strict rem)
|
|
| (Id id, None, rhs) :: rem ->
|
|
Llet (Strict, Pgenval, id, rhs, bind_strict rem)
|
|
| (_id, Some _, _rhs) :: rem -> bind_strict rem
|
|
and patch_forwards = function
|
|
| [] -> cont
|
|
| (Ignore_loc _, _, _rhs) :: rem | (_, None, _rhs) :: rem ->
|
|
patch_forwards rem
|
|
| (Id id, Some (_loc, shape), rhs) :: rem ->
|
|
Lsequence
|
|
( Lapply
|
|
{
|
|
ap_loc = Loc_unknown;
|
|
ap_func = mod_prim "update_mod";
|
|
ap_args = [ shape; Lvar id; rhs ];
|
|
ap_tailcall = Default_tailcall;
|
|
ap_inlined = Default_inline;
|
|
ap_specialised = Default_specialise;
|
|
},
|
|
patch_forwards rem )
|
|
in
|
|
bind_inits bindings
|
|
|
|
let compile_recmodule ~scopes compile_rhs bindings cont =
|
|
eval_rec_bindings
|
|
(reorder_rec_bindings
|
|
(List.map
|
|
(fun { mb_id = id; mb_name; mb_expr = modl; mb_loc = loc; _ } ->
|
|
let id_or_ignore_loc, shape =
|
|
match id with
|
|
| None ->
|
|
let loc = of_location ~scopes mb_name.loc in
|
|
(Ignore_loc loc, Result.Error Unnamed)
|
|
| Some id -> (Id id, init_shape id modl)
|
|
in
|
|
(id_or_ignore_loc, modl.mod_loc, shape, compile_rhs id modl loc))
|
|
bindings))
|
|
cont
|
|
|
|
(* Code to translate class entries in a structure *)
|
|
|
|
let transl_class_bindings ~scopes cl_list =
|
|
let ids = List.map (fun (ci, _) -> ci.ci_id_class) cl_list in
|
|
( ids,
|
|
List.map
|
|
(fun ({ ci_id_class = id; ci_expr = cl; ci_virt = vf; _}, meths) ->
|
|
(id, transl_class ~scopes ids id meths cl vf))
|
|
cl_list )
|
|
|
|
(* Compile one or more functors, merging curried functors to produce
|
|
multi-argument functors. Any [@inline] attribute on a functor that is
|
|
merged must be consistent with any other [@inline] attribute(s) on the
|
|
functor(s) being merged with. Such an attribute will be placed on the
|
|
resulting merged functor. *)
|
|
|
|
let merge_inline_attributes attr1 attr2 loc =
|
|
match Lambda.merge_inline_attributes attr1 attr2 with
|
|
| Some attr -> attr
|
|
| None -> raise (Error (to_location loc, Conflicting_inline_attributes))
|
|
|
|
let merge_functors ~scopes mexp coercion root_path =
|
|
let rec merge ~scopes mexp coercion path acc inline_attribute =
|
|
let finished = (acc, mexp, path, coercion, inline_attribute) in
|
|
match mexp.mod_desc with
|
|
| Tmod_functor (param, body) ->
|
|
let inline_attribute' =
|
|
Translattribute.get_inline_attribute mexp.mod_attributes
|
|
in
|
|
let arg_coercion, res_coercion =
|
|
match coercion with
|
|
| Tcoerce_none -> (Tcoerce_none, Tcoerce_none)
|
|
| Tcoerce_functor (arg_coercion, res_coercion) ->
|
|
(arg_coercion, res_coercion)
|
|
| _ -> fatal_error "Translmod.merge_functors: bad coercion"
|
|
in
|
|
let loc = of_location ~scopes mexp.mod_loc in
|
|
let path, param =
|
|
match param with
|
|
| Unit -> (None, Ident.create_local "*")
|
|
| Named (None, _, _) ->
|
|
let id = Ident.create_local "_" in
|
|
(functor_path path id, id)
|
|
| Named (Some id, _, _) -> (functor_path path id, id)
|
|
in
|
|
let inline_attribute =
|
|
merge_inline_attributes inline_attribute inline_attribute' loc
|
|
in
|
|
merge ~scopes body res_coercion path
|
|
((param, loc, arg_coercion) :: acc)
|
|
inline_attribute
|
|
| _ -> finished
|
|
in
|
|
merge ~scopes mexp coercion root_path [] Default_inline
|
|
|
|
let rec compile_functor ~scopes mexp coercion root_path loc =
|
|
let functor_params_rev, body, body_path, res_coercion, inline_attribute =
|
|
merge_functors ~scopes mexp coercion root_path
|
|
in
|
|
assert (List.length functor_params_rev >= 1);
|
|
(* cf. [transl_module] *)
|
|
let params, body =
|
|
List.fold_left
|
|
(fun (params, body) (param, loc, arg_coercion) ->
|
|
let param' = Ident.rename param in
|
|
let arg = apply_coercion loc Alias arg_coercion (Lvar param') in
|
|
let params = (param', Pgenval) :: params in
|
|
let body = Llet (Alias, Pgenval, param, arg, body) in
|
|
(params, body))
|
|
([], transl_module ~scopes res_coercion body_path body)
|
|
functor_params_rev
|
|
in
|
|
Lfunction
|
|
{
|
|
kind = Curried;
|
|
params;
|
|
return = Pgenval;
|
|
attr =
|
|
{
|
|
inline = inline_attribute;
|
|
specialise = Default_specialise;
|
|
local = Default_local;
|
|
is_a_functor = true;
|
|
stub = false;
|
|
};
|
|
loc;
|
|
body;
|
|
}
|
|
|
|
(* Compile a module expression *)
|
|
|
|
and transl_module ~scopes cc rootpath mexp =
|
|
List.iter (Translattribute.check_attribute_on_module mexp) mexp.mod_attributes;
|
|
let loc = of_location ~scopes mexp.mod_loc in
|
|
match mexp.mod_desc with
|
|
| Tmod_ident (path, _) ->
|
|
apply_coercion loc Strict cc (transl_module_path loc mexp.mod_env path)
|
|
| Tmod_structure str -> fst (transl_struct ~scopes loc [] cc rootpath str)
|
|
| Tmod_functor _ ->
|
|
oo_wrap mexp.mod_env true
|
|
(fun () -> compile_functor ~scopes mexp cc rootpath loc)
|
|
()
|
|
| Tmod_apply (funct, arg, ccarg) ->
|
|
let inlined_attribute, funct =
|
|
Translattribute.get_and_remove_inlined_attribute_on_module funct
|
|
in
|
|
oo_wrap mexp.mod_env true
|
|
(apply_coercion loc Strict cc)
|
|
(Lapply
|
|
{
|
|
ap_loc = loc;
|
|
ap_func = transl_module ~scopes Tcoerce_none None funct;
|
|
ap_args = [ transl_module ~scopes ccarg None arg ];
|
|
ap_tailcall = Default_tailcall;
|
|
ap_inlined = inlined_attribute;
|
|
ap_specialised = Default_specialise;
|
|
})
|
|
| Tmod_constraint (arg, _, _, ccarg) ->
|
|
transl_module ~scopes (compose_coercions cc ccarg) rootpath arg
|
|
| Tmod_unpack (arg, _) ->
|
|
apply_coercion loc Strict cc (Translcore.transl_exp ~scopes arg)
|
|
|
|
and transl_struct ~scopes loc fields cc rootpath { str_final_env; str_items; _ }
|
|
=
|
|
transl_structure ~scopes loc fields cc rootpath str_final_env str_items
|
|
|
|
(* The function transl_structure is called by the bytecode compiler.
|
|
Some effort is made to compile in top to bottom order, in order to display
|
|
warning by increasing locations. *)
|
|
and transl_structure ~scopes loc fields cc rootpath final_env = function
|
|
| [] ->
|
|
let body, size =
|
|
match cc with
|
|
| Tcoerce_none ->
|
|
( Lprim
|
|
( Pmakeblock (0, Immutable, None),
|
|
List.map (fun id -> Lvar id) (List.rev fields),
|
|
loc ),
|
|
List.length fields )
|
|
| Tcoerce_structure (pos_cc_list, id_pos_list) ->
|
|
(* Do not ignore id_pos_list ! *)
|
|
(*Format.eprintf "%a@.@[" Includemod.print_coercion cc;
|
|
List.iter (fun l -> Format.eprintf "%a@ " Ident.print l)
|
|
fields;
|
|
Format.eprintf "@]@.";*)
|
|
let v = Array.of_list (List.rev fields) in
|
|
let get_field pos = if pos < 0 then lambda_unit else Lvar v.(pos) in
|
|
let ids = List.fold_right Ident.Set.add fields Ident.Set.empty in
|
|
let lam =
|
|
Lprim
|
|
( Pmakeblock (0, Immutable, None),
|
|
List.map
|
|
(fun (pos, cc) ->
|
|
match cc with
|
|
| Tcoerce_primitive p ->
|
|
Translprim.transl_primitive
|
|
(of_location ~scopes p.pc_loc)
|
|
p.pc_desc p.pc_env p.pc_type None
|
|
| _ -> apply_coercion loc Strict cc (get_field pos))
|
|
pos_cc_list,
|
|
loc )
|
|
and id_pos_list =
|
|
List.filter
|
|
(fun (id, _, _) -> not (Ident.Set.mem id ids))
|
|
id_pos_list
|
|
in
|
|
( wrap_id_pos_list loc id_pos_list get_field lam,
|
|
List.length pos_cc_list )
|
|
| _ -> fatal_error "Translmod.transl_structure"
|
|
in
|
|
(* This debugging event provides information regarding the structure
|
|
items. It is ignored by the OCaml debugger but is used by
|
|
Js_of_ocaml to preserve variable names. *)
|
|
( (if !Clflags.debug && not !Clflags.native_code then
|
|
Levent
|
|
( body,
|
|
{
|
|
lev_loc = loc;
|
|
lev_kind = Lev_pseudo;
|
|
lev_repr = None;
|
|
lev_env = final_env;
|
|
} )
|
|
else body),
|
|
size )
|
|
| item :: rem -> (
|
|
match item.str_desc with
|
|
| Tstr_eval (expr, _) ->
|
|
let body, size =
|
|
transl_structure ~scopes loc fields cc rootpath final_env rem
|
|
in
|
|
(Lsequence (transl_exp ~scopes expr, body), size)
|
|
| Tstr_value (rec_flag, pat_expr_list) ->
|
|
(* Translate bindings first *)
|
|
let mk_lam_let =
|
|
transl_let ~scopes ~in_structure:true rec_flag pat_expr_list
|
|
in
|
|
let ext_fields =
|
|
List.rev_append (let_bound_idents pat_expr_list) fields
|
|
in
|
|
(* Then, translate remainder of struct *)
|
|
let body, size =
|
|
transl_structure ~scopes loc ext_fields cc rootpath final_env rem
|
|
in
|
|
(mk_lam_let body, size)
|
|
| Tstr_primitive descr ->
|
|
record_primitive descr.val_val;
|
|
transl_structure ~scopes loc fields cc rootpath final_env rem
|
|
| Tstr_type _ ->
|
|
transl_structure ~scopes loc fields cc rootpath final_env rem
|
|
| Tstr_typext tyext ->
|
|
let ids = List.map (fun ext -> ext.ext_id) tyext.tyext_constructors in
|
|
let body, size =
|
|
transl_structure ~scopes loc
|
|
(List.rev_append ids fields)
|
|
cc rootpath final_env rem
|
|
in
|
|
(transl_type_extension ~scopes item.str_env rootpath tyext body, size)
|
|
| Tstr_exception ext ->
|
|
let id = ext.tyexn_constructor.ext_id in
|
|
let path = field_path rootpath id in
|
|
let body, size =
|
|
transl_structure ~scopes loc (id :: fields) cc rootpath final_env
|
|
rem
|
|
in
|
|
( Llet
|
|
( Strict,
|
|
Pgenval,
|
|
id,
|
|
transl_extension_constructor ~scopes item.str_env path
|
|
ext.tyexn_constructor,
|
|
body ),
|
|
size )
|
|
| Tstr_module ({ mb_presence = Mp_present; _} as mb) -> (
|
|
let id = mb.mb_id in
|
|
(* Translate module first *)
|
|
let subscopes =
|
|
match id with
|
|
| None -> scopes
|
|
| Some id -> enter_module_definition ~scopes id
|
|
in
|
|
let module_body =
|
|
transl_module ~scopes:subscopes Tcoerce_none
|
|
(Option.bind id (field_path rootpath))
|
|
mb.mb_expr
|
|
in
|
|
let module_body =
|
|
Translattribute.add_inline_attribute module_body mb.mb_loc
|
|
mb.mb_attributes
|
|
in
|
|
(* Translate remainder second *)
|
|
let body, size =
|
|
transl_structure ~scopes loc (cons_opt id fields) cc rootpath
|
|
final_env rem
|
|
in
|
|
match id with
|
|
| None ->
|
|
( Lsequence
|
|
( Lprim
|
|
( Pignore,
|
|
[ module_body ],
|
|
of_location ~scopes mb.mb_name.loc ),
|
|
body ),
|
|
size )
|
|
| Some id ->
|
|
let module_body =
|
|
Levent
|
|
( module_body,
|
|
{
|
|
lev_loc = of_location ~scopes mb.mb_loc;
|
|
lev_kind = Lev_module_definition id;
|
|
lev_repr = None;
|
|
lev_env = Env.empty;
|
|
} )
|
|
in
|
|
( Llet (pure_module mb.mb_expr, Pgenval, id, module_body, body),
|
|
size ))
|
|
| Tstr_module ({ mb_presence = Mp_absent; _} as mb) ->
|
|
List.iter
|
|
(Translattribute.check_attribute_on_module mb.mb_expr)
|
|
mb.mb_attributes;
|
|
List.iter
|
|
(Translattribute.check_attribute_on_module mb.mb_expr)
|
|
mb.mb_expr.mod_attributes;
|
|
transl_structure ~scopes loc fields cc rootpath final_env rem
|
|
| Tstr_recmodule bindings ->
|
|
let ext_fields =
|
|
List.rev_append
|
|
(List.filter_map (fun mb -> mb.mb_id) bindings)
|
|
fields
|
|
in
|
|
let body, size =
|
|
transl_structure ~scopes loc ext_fields cc rootpath final_env rem
|
|
in
|
|
let lam =
|
|
compile_recmodule ~scopes
|
|
(fun id modl loc ->
|
|
match id with
|
|
| None -> transl_module ~scopes Tcoerce_none None modl
|
|
| Some id ->
|
|
let module_body =
|
|
transl_module
|
|
~scopes:(enter_module_definition ~scopes id)
|
|
Tcoerce_none (field_path rootpath id) modl
|
|
in
|
|
Levent
|
|
( module_body,
|
|
{
|
|
lev_loc = of_location ~scopes loc;
|
|
lev_kind = Lev_module_definition id;
|
|
lev_repr = None;
|
|
lev_env = Env.empty;
|
|
} ))
|
|
bindings body
|
|
in
|
|
(lam, size)
|
|
| Tstr_class cl_list ->
|
|
let ids, class_bindings = transl_class_bindings ~scopes cl_list in
|
|
let body, size =
|
|
transl_structure ~scopes loc
|
|
(List.rev_append ids fields)
|
|
cc rootpath final_env rem
|
|
in
|
|
(Lletrec (class_bindings, body), size)
|
|
| Tstr_include incl ->
|
|
let ids = bound_value_identifiers incl.incl_type in
|
|
let modl = incl.incl_mod in
|
|
let mid = Ident.create_local "include" in
|
|
let rec rebind_idents pos newfields = function
|
|
| [] ->
|
|
transl_structure ~scopes loc newfields cc rootpath final_env rem
|
|
| id :: ids ->
|
|
let body, size =
|
|
rebind_idents (pos + 1) (id :: newfields) ids
|
|
in
|
|
( Llet
|
|
( Alias,
|
|
Pgenval,
|
|
id,
|
|
Lprim
|
|
( Pfield pos,
|
|
[ Lvar mid ],
|
|
of_location ~scopes incl.incl_loc ),
|
|
body ),
|
|
size )
|
|
in
|
|
let body, size = rebind_idents 0 fields ids in
|
|
( Llet
|
|
( pure_module modl,
|
|
Pgenval,
|
|
mid,
|
|
transl_module ~scopes Tcoerce_none None modl,
|
|
body ),
|
|
size )
|
|
| Tstr_open od -> (
|
|
let pure = pure_module od.open_expr in
|
|
(* this optimization shouldn't be needed because Simplif would
|
|
actually remove the [Llet] when it's not used.
|
|
But since [scan_used_globals] runs before Simplif, we need to do
|
|
it. *)
|
|
match od.open_bound_items with
|
|
| [] when pure = Alias ->
|
|
transl_structure ~scopes loc fields cc rootpath final_env rem
|
|
| _ ->
|
|
let ids = bound_value_identifiers od.open_bound_items in
|
|
let mid = Ident.create_local "open" in
|
|
let rec rebind_idents pos newfields = function
|
|
| [] ->
|
|
transl_structure ~scopes loc newfields cc rootpath final_env
|
|
rem
|
|
| id :: ids ->
|
|
let body, size =
|
|
rebind_idents (pos + 1) (id :: newfields) ids
|
|
in
|
|
( Llet
|
|
( Alias,
|
|
Pgenval,
|
|
id,
|
|
Lprim
|
|
( Pfield pos,
|
|
[ Lvar mid ],
|
|
of_location ~scopes od.open_loc ),
|
|
body ),
|
|
size )
|
|
in
|
|
let body, size = rebind_idents 0 fields ids in
|
|
( Llet
|
|
( pure,
|
|
Pgenval,
|
|
mid,
|
|
transl_module ~scopes Tcoerce_none None od.open_expr,
|
|
body ),
|
|
size ))
|
|
| Tstr_modtype _ | Tstr_class_type _ | Tstr_attribute _ ->
|
|
transl_structure ~scopes loc fields cc rootpath final_env rem)
|
|
|
|
(* Compile a toplevel phrase *)
|
|
|
|
let toploop_ident = Ident.create_persistent "Topinf"
|
|
|
|
let toploop_getvalue_pos = 0 (* position of getvalue in module Topinf *)
|
|
|
|
let toploop_setvalue_pos = 1 (* position of setvalue in module Topinf *)
|
|
|
|
let aliased_idents = ref Ident.empty
|
|
|
|
let set_toplevel_unique_name id =
|
|
aliased_idents := Ident.add id (Ident.unique_toplevel_name id) !aliased_idents
|
|
|
|
let toplevel_name id =
|
|
try Ident.find_same id !aliased_idents with Not_found -> Ident.name id
|
|
|
|
let toploop_getvalue id =
|
|
Lapply
|
|
{
|
|
ap_loc = Loc_unknown;
|
|
ap_func =
|
|
Lprim
|
|
( Pfield toploop_getvalue_pos,
|
|
[ Lprim (Pgetglobal toploop_ident, [], Loc_unknown) ],
|
|
Loc_unknown );
|
|
ap_args =
|
|
[
|
|
Lconst
|
|
(Const_base (Const_string (toplevel_name id, Location.none, None)));
|
|
];
|
|
ap_tailcall = Default_tailcall;
|
|
ap_inlined = Default_inline;
|
|
ap_specialised = Default_specialise;
|
|
}
|
|
|
|
let toploop_setvalue id lam =
|
|
Lapply
|
|
{
|
|
ap_loc = Loc_unknown;
|
|
ap_func =
|
|
Lprim
|
|
( Pfield toploop_setvalue_pos,
|
|
[ Lprim (Pgetglobal toploop_ident, [], Loc_unknown) ],
|
|
Loc_unknown );
|
|
ap_args =
|
|
[
|
|
Lconst
|
|
(Const_base (Const_string (toplevel_name id, Location.none, None)));
|
|
lam;
|
|
];
|
|
ap_tailcall = Default_tailcall;
|
|
ap_inlined = Default_inline;
|
|
ap_specialised = Default_specialise;
|
|
}
|
|
|
|
let toploop_setvalue_id id =
|
|
toploop_setvalue id (Lambda.Lvar id)
|
|
|
|
let close_toplevel_term (lam, ()) =
|
|
Ident.Set.fold
|
|
(fun id l -> Lambda.Llet (Strict, Pgenval, id, toploop_getvalue id, l))
|
|
(Lambda.free_variables lam)
|
|
lam
|
|
|
|
let transl_toplevel_item ~scopes (item : structure_item) =
|
|
match item.str_desc with
|
|
| Tstr_eval (expr, _)
|
|
| Tstr_value
|
|
(Nonrecursive, [ { vb_pat = { pat_desc = Tpat_any; _}; vb_expr = expr; _} ])
|
|
->
|
|
(* special compilation for toplevel "let _ = expr", so
|
|
that Toploop can display the result of the expression.
|
|
Otherwise, the normal compilation would result
|
|
in a Lsequence returning unit. *)
|
|
Translcore.transl_exp ~scopes expr
|
|
| Tstr_value (rec_flag, pat_expr_list) ->
|
|
let idents = let_bound_idents pat_expr_list in
|
|
transl_let ~scopes ~in_structure:true rec_flag pat_expr_list
|
|
(make_sequence toploop_setvalue_id idents)
|
|
| Tstr_typext tyext ->
|
|
let idents = List.map (fun ext -> ext.ext_id) tyext.tyext_constructors in
|
|
(* we need to use unique name in case of multiple
|
|
definitions of the same extension constructor in the toplevel *)
|
|
List.iter set_toplevel_unique_name idents;
|
|
transl_type_extension ~scopes item.str_env None tyext
|
|
(make_sequence toploop_setvalue_id idents)
|
|
| Tstr_exception ext ->
|
|
set_toplevel_unique_name ext.tyexn_constructor.ext_id;
|
|
toploop_setvalue ext.tyexn_constructor.ext_id
|
|
(transl_extension_constructor ~scopes item.str_env None
|
|
ext.tyexn_constructor)
|
|
| Tstr_module { mb_id = None; mb_presence = Mp_present; mb_expr = modl; _} ->
|
|
transl_module ~scopes Tcoerce_none None modl
|
|
| Tstr_module { mb_id = Some id; mb_presence = Mp_present; mb_expr = modl; _} ->
|
|
(* we need to use the unique name for the module because of issues
|
|
with "open" (PR#8133) *)
|
|
set_toplevel_unique_name id;
|
|
let lam =
|
|
transl_module
|
|
~scopes:(enter_module_definition ~scopes id)
|
|
Tcoerce_none (Some (Pident id)) modl
|
|
in
|
|
toploop_setvalue id lam
|
|
| Tstr_recmodule bindings ->
|
|
let idents = List.filter_map (fun mb -> mb.mb_id) bindings in
|
|
compile_recmodule ~scopes
|
|
(fun id modl _loc ->
|
|
match id with
|
|
| None -> transl_module ~scopes Tcoerce_none None modl
|
|
| Some id ->
|
|
transl_module
|
|
~scopes:(enter_module_definition ~scopes id)
|
|
Tcoerce_none (Some (Pident id)) modl)
|
|
bindings
|
|
(make_sequence toploop_setvalue_id idents)
|
|
| Tstr_class cl_list ->
|
|
(* we need to use unique names for the classes because there might
|
|
be a value named identically *)
|
|
let ids, class_bindings = transl_class_bindings ~scopes cl_list in
|
|
List.iter set_toplevel_unique_name ids;
|
|
Lletrec (class_bindings, make_sequence toploop_setvalue_id ids)
|
|
| Tstr_include incl ->
|
|
let ids = bound_value_identifiers incl.incl_type in
|
|
let modl = incl.incl_mod in
|
|
let mid = Ident.create_local "include" in
|
|
let rec set_idents pos = function
|
|
| [] -> lambda_unit
|
|
| id :: ids ->
|
|
Lsequence
|
|
( toploop_setvalue id
|
|
(Lprim (Pfield pos, [ Lvar mid ], Loc_unknown)),
|
|
set_idents (pos + 1) ids )
|
|
in
|
|
Llet
|
|
( Strict,
|
|
Pgenval,
|
|
mid,
|
|
transl_module ~scopes Tcoerce_none None modl,
|
|
set_idents 0 ids )
|
|
| Tstr_primitive descr ->
|
|
record_primitive descr.val_val;
|
|
lambda_unit
|
|
| Tstr_open od -> (
|
|
let pure = pure_module od.open_expr in
|
|
(* this optimization shouldn't be needed because Simplif would
|
|
actually remove the [Llet] when it's not used.
|
|
But since [scan_used_globals] runs before Simplif, we need to do
|
|
it. *)
|
|
match od.open_bound_items with
|
|
| [] when pure = Alias -> lambda_unit
|
|
| _ ->
|
|
let ids = bound_value_identifiers od.open_bound_items in
|
|
let mid = Ident.create_local "open" in
|
|
let rec set_idents pos = function
|
|
| [] -> lambda_unit
|
|
| id :: ids ->
|
|
Lsequence
|
|
( toploop_setvalue id
|
|
(Lprim (Pfield pos, [ Lvar mid ], Loc_unknown)),
|
|
set_idents (pos + 1) ids )
|
|
in
|
|
Llet
|
|
( pure,
|
|
Pgenval,
|
|
mid,
|
|
transl_module ~scopes Tcoerce_none None od.open_expr,
|
|
set_idents 0 ids ))
|
|
| Tstr_module ({ mb_presence = Mp_absent; _} as mb) ->
|
|
List.iter
|
|
(Translattribute.check_attribute_on_module mb.mb_expr)
|
|
mb.mb_attributes;
|
|
List.iter
|
|
(Translattribute.check_attribute_on_module mb.mb_expr)
|
|
mb.mb_expr.mod_attributes;
|
|
lambda_unit
|
|
| Tstr_modtype _ | Tstr_type _ | Tstr_class_type _ | Tstr_attribute _ ->
|
|
lambda_unit
|
|
|
|
let transl_toplevel_item_and_close ~scopes itm =
|
|
close_toplevel_term
|
|
(transl_label_init (fun () -> (transl_toplevel_item ~scopes itm, ())))
|
|
|
|
let transl_toplevel_definition str =
|
|
Translobj.reset_labels ();
|
|
Translprim.clear_used_primitives ();
|
|
Lambda.make_sequence
|
|
(transl_toplevel_item_and_close ~scopes:empty_scopes)
|
|
str.str_items
|
|
|
|
(* Return the value referred to by a path *)
|
|
|
|
let rec eval_address = function
|
|
| Env.Aident id -> (
|
|
if Ident.persistent id || Ident.global id then
|
|
Symtable.get_global_value id
|
|
else
|
|
let name = toplevel_name id in
|
|
try String.Map.find name !toplevel_value_bindings
|
|
with Not_found ->
|
|
raise (Symtable.Error (Symtable.Undefined_global name)))
|
|
| Env.Adot (p, pos) -> Obj.field (eval_address p) pos
|
|
|
|
let eval_path find env path =
|
|
match find path env with
|
|
| addr -> eval_address addr
|
|
| exception Not_found ->
|
|
fatal_error ("Cannot find address for: " ^ Path.name path)
|
|
|
|
let eval_module_path env path = eval_path Env.find_module_address env path
|
|
|
|
let eval_value_path env path = eval_path Env.find_value_address env path
|
|
|
|
let eval_extension_path env path =
|
|
eval_path Env.find_constructor_address env path
|
|
|
|
let eval_class_path env path = eval_path Env.find_class_address env path
|
|
|
|
(* To print values *)
|
|
|
|
module EvalPath = struct
|
|
type valu = Obj.t
|
|
|
|
exception Error
|
|
|
|
let eval_address addr =
|
|
try eval_address addr with Symtable.Error _ -> raise Error
|
|
|
|
let same_value v1 v2 = v1 == v2
|
|
end
|
|
|
|
module Printer = Genprintval.Make (Obj) (EvalPath)
|
|
|
|
let max_printer_depth = ref 100
|
|
|
|
let max_printer_steps = ref 300
|
|
|
|
let print_out_value = Oprint.out_value
|
|
|
|
let print_out_type = Oprint.out_type
|
|
|
|
let print_out_class_type = Oprint.out_class_type
|
|
|
|
let print_out_module_type = Oprint.out_module_type
|
|
|
|
let print_out_type_extension = Oprint.out_type_extension
|
|
|
|
let print_out_sig_item = Oprint.out_sig_item
|
|
|
|
let print_out_signature = Oprint.out_signature
|
|
|
|
let print_out_phrase = Oprint.out_phrase
|
|
|
|
let print_untyped_exception ppf obj =
|
|
!print_out_value ppf (Printer.outval_of_untyped_exception obj)
|
|
|
|
let outval_of_value env obj ty =
|
|
Printer.outval_of_value !max_printer_steps !max_printer_depth
|
|
(fun _ _ _ -> None)
|
|
env obj ty
|
|
|
|
let print_value env obj ppf ty =
|
|
!print_out_value ppf (outval_of_value env obj ty)
|
|
|
|
type ('a, 'b) gen_printer = ('a, 'b) Genprintval.gen_printer =
|
|
| Zero of 'b
|
|
| Succ of ('a -> ('a, 'b) gen_printer)
|
|
|
|
let install_printer = Printer.install_printer
|
|
|
|
let install_generic_printer = Printer.install_generic_printer
|
|
|
|
let install_generic_printer' = Printer.install_generic_printer'
|
|
|
|
let remove_printer = Printer.remove_printer
|
|
|
|
(* Hooks for parsing functions *)
|
|
|
|
let parse_toplevel_phrase = ref Parse.toplevel_phrase
|
|
|
|
let parse_use_file = ref Parse.use_file
|
|
|
|
let print_location = Location.print_loc
|
|
|
|
let print_error = Location.print_report
|
|
|
|
let print_warning = Location.print_warning
|
|
|
|
let input_name = Location.input_name
|
|
|
|
let parse_mod_use_file name lb =
|
|
let modname =
|
|
String.capitalize_ascii (Filename.remove_extension (Filename.basename name))
|
|
in
|
|
let items =
|
|
List.concat
|
|
(List.map
|
|
(function Ptop_def s -> s | Ptop_dir _ -> [])
|
|
(!parse_use_file lb))
|
|
in
|
|
[
|
|
Ptop_def
|
|
[
|
|
Str.module_
|
|
(Mb.mk (Location.mknoloc (Some modname)) (Mod.structure items));
|
|
];
|
|
]
|
|
|
|
(* Hook for initialization *)
|
|
|
|
let toplevel_startup_hook = ref (fun () -> ())
|
|
|
|
type event = ..
|
|
|
|
type event += Startup | After_setup
|
|
|
|
let hooks = ref []
|
|
|
|
let add_hook f = hooks := f :: !hooks
|
|
|
|
let () = add_hook (function Startup -> !toplevel_startup_hook () | _ -> ())
|
|
|
|
let run_hooks hook = List.iter (fun f -> f hook) !hooks
|
|
|
|
(* Load in-core and execute a lambda term *)
|
|
|
|
let outval_of_value env obj ty =
|
|
Printer.outval_of_value !max_printer_steps !max_printer_depth
|
|
(fun _ _ _ -> None)
|
|
env obj ty
|
|
|
|
let may_trace = ref false (* Global lock on tracing *)
|
|
|
|
type evaluation_outcome = Result of Obj.t | Exception of exn
|
|
|
|
let backtrace = ref None
|
|
|
|
let record_backtrace () =
|
|
if Printexc.backtrace_status () then
|
|
backtrace := Some (Printexc.get_backtrace ())
|
|
|
|
let load_lambda ppf lam =
|
|
if !Clflags.dump_rawlambda then
|
|
Format.fprintf ppf "%a@." Printlambda.lambda lam;
|
|
let slam = Simplif.simplify_lambda lam in
|
|
if !Clflags.dump_lambda then Format.fprintf ppf "%a@." Printlambda.lambda slam;
|
|
let init_code, fun_code = Bytegen.compile_phrase slam in
|
|
if !Clflags.dump_instr then
|
|
Format.fprintf ppf "%a%a@." Printinstr.instrlist init_code
|
|
Printinstr.instrlist fun_code;
|
|
let code, reloc, events = Emitcode.to_memory init_code fun_code in
|
|
let can_free = fun_code = [] in
|
|
let initial_symtable = Symtable.current_state () in
|
|
Symtable.patch_object code reloc;
|
|
Symtable.check_global_initialized reloc;
|
|
Symtable.update_global_table ();
|
|
let initial_bindings = !toplevel_value_bindings in
|
|
let bytecode, closure = Meta.reify_bytecode code [| events |] None in
|
|
match
|
|
may_trace := true;
|
|
Fun.protect
|
|
~finally:(fun () ->
|
|
may_trace := false;
|
|
if can_free then Meta.release_bytecode bytecode)
|
|
closure
|
|
with
|
|
| retval -> Result retval
|
|
| exception x ->
|
|
record_backtrace ();
|
|
toplevel_value_bindings := initial_bindings;
|
|
(* PR#6211 *)
|
|
Symtable.restore_state initial_symtable;
|
|
Exception x
|
|
|
|
(* Print the outcome of an evaluation *)
|
|
let pr_item =
|
|
Printtyp.print_items (fun env -> function
|
|
| Sig_value (id, { val_kind = Val_reg; val_type; _}, _) ->
|
|
Some (outval_of_value env (getvalue (toplevel_name id)) val_type)
|
|
| _ -> None)
|
|
|
|
let read_interactive_input = ref (fun _ _ -> 0)
|
|
|
|
(* Toplevel initialization. Performed here instead of at the
|
|
beginning of loop() so that user code linked in with ocamlmktop
|
|
can call directives from Topdirs. *)
|
|
let _ =
|
|
(*if !Sys.interactive then (* PR#6108 *)
|
|
invalid_arg "The ocamltoplevel.cma library from compiler-libs \
|
|
cannot be loaded inside the OCaml toplevel"; *)
|
|
Sys.interactive := true;
|
|
let crc_intfs = Symtable.init_toplevel () in
|
|
Compmisc.init_path ();
|
|
Env.import_crcs ~source:Sys.executable_name crc_intfs;
|
|
()
|
|
|
|
(* The current typing environment for the toplevel *)
|
|
let toplevel_env = ref Env.empty
|
|
|
|
let initialize_toplevel_env () = toplevel_env := Compmisc.initial_env ()
|
|
|
|
let set_paths () =
|
|
(* Add whatever -I options have been specified on the command line,
|
|
but keep the directories that user code linked in with ocamlmktop
|
|
may have added to load_path. *)
|
|
let expand = Misc.expand_directory Config.standard_library in
|
|
let current_load_path = Load_path.get_paths () in
|
|
let load_path =
|
|
List.concat
|
|
[
|
|
[ "" ];
|
|
List.map expand (List.rev !Compenv.first_include_dirs);
|
|
List.map expand (List.rev !Clflags.include_dirs);
|
|
List.map expand (List.rev !Compenv.last_include_dirs);
|
|
current_load_path;
|
|
[ expand "+camlp4" ];
|
|
]
|
|
in
|
|
Load_path.init load_path;
|
|
Dll.add_path load_path
|
|
|
|
(* Print an exception produced by an evaluation *)
|
|
|
|
let print_out_exception ppf exn outv =
|
|
!print_out_phrase ppf (Ophr_exception (exn, outv))
|
|
|
|
let print_exception_outcome ppf exn =
|
|
if exn = Out_of_memory then Gc.full_major ();
|
|
let outv = outval_of_value !toplevel_env (Obj.repr exn) Predef.type_exn in
|
|
print_out_exception ppf exn outv;
|
|
if Printexc.backtrace_status () then
|
|
match !backtrace with
|
|
| None -> ()
|
|
| Some b ->
|
|
print_string b;
|
|
backtrace := None
|
|
|
|
(* Inserting new toplevel directives *)
|
|
let directive_table = (Hashtbl.create 23 : (string, directive_fun) Hashtbl.t)
|
|
|
|
let directive_info_table =
|
|
(Hashtbl.create 23 : (string, directive_info) Hashtbl.t)
|
|
|
|
let add_directive name dir_fun dir_info =
|
|
Hashtbl.add directive_table name dir_fun;
|
|
Hashtbl.add directive_info_table name dir_info
|
|
|
|
(* Execute a toplevel phrase *)
|
|
let execute_phrase print_outcome ppf phr =
|
|
match phr with
|
|
| Ptop_def sstr -> (
|
|
let oldenv = !toplevel_env in
|
|
Typecore.reset_delayed_checks ();
|
|
let str, sg, sn, newenv = Typemod.type_toplevel_phrase oldenv sstr in
|
|
if !Clflags.dump_typedtree then Printtyped.implementation ppf str;
|
|
let sg' = Typemod.Signature_names.simplify newenv sn sg in
|
|
ignore (Includemod.signatures ~mark:Mark_positive oldenv sg sg');
|
|
Typecore.force_delayed_checks ();
|
|
let lam = transl_toplevel_definition str in
|
|
Warnings.check_fatal ();
|
|
try
|
|
toplevel_env := newenv;
|
|
let res = load_lambda ppf lam in
|
|
let out_phr =
|
|
match res with
|
|
| Result v ->
|
|
if print_outcome then
|
|
Printtyp.wrap_printing_env ~error:false oldenv (fun () ->
|
|
match str.str_items with
|
|
| [
|
|
{
|
|
str_desc =
|
|
( Tstr_eval (exp, _)
|
|
| Tstr_value
|
|
( Asttypes.Nonrecursive,
|
|
[
|
|
{
|
|
vb_pat = { pat_desc = Tpat_any; _};
|
|
vb_expr = exp; _
|
|
};
|
|
] ) ); _
|
|
};
|
|
] ->
|
|
let outv = outval_of_value newenv v exp.exp_type in
|
|
let ty = Printtyp.tree_of_type_scheme exp.exp_type in
|
|
Ophr_eval (outv, ty)
|
|
| [] -> Ophr_signature []
|
|
| _ -> Ophr_signature (pr_item oldenv sg'))
|
|
else Ophr_signature []
|
|
| Exception exn ->
|
|
toplevel_env := oldenv;
|
|
if exn = Out_of_memory then Gc.full_major ();
|
|
let outv =
|
|
outval_of_value !toplevel_env (Obj.repr exn) Predef.type_exn
|
|
in
|
|
Ophr_exception (exn, outv)
|
|
in
|
|
!print_out_phrase ppf out_phr;
|
|
(if Printexc.backtrace_status () then
|
|
match !backtrace with
|
|
| None -> ()
|
|
| Some b ->
|
|
pp_print_string ppf b;
|
|
pp_print_flush ppf ();
|
|
backtrace := None);
|
|
match out_phr with
|
|
| Ophr_eval (_, _) | Ophr_signature _ -> true
|
|
| Ophr_exception _ -> false
|
|
with x ->
|
|
toplevel_env := oldenv;
|
|
raise x)
|
|
| Ptop_dir { pdir_name = { Location.txt = dir_name; _}; pdir_arg; _} -> (
|
|
let d =
|
|
try Some (Hashtbl.find directive_table dir_name)
|
|
with Not_found -> None
|
|
in
|
|
match d with
|
|
| None ->
|
|
fprintf ppf "Unknown directive `%s'." dir_name;
|
|
let directives =
|
|
Hashtbl.fold (fun dir _ acc -> dir :: acc) directive_table []
|
|
in
|
|
Misc.did_you_mean ppf (fun () -> Misc.spellcheck directives dir_name);
|
|
fprintf ppf "@.";
|
|
false
|
|
| Some d -> (
|
|
match (d, pdir_arg) with
|
|
| Directive_none f, None ->
|
|
f ();
|
|
true
|
|
| Directive_string f, Some { pdira_desc = Pdir_string s; _} ->
|
|
f s;
|
|
true
|
|
| Directive_int f, Some { pdira_desc = Pdir_int (n, None); _} -> (
|
|
match Int_literal_converter.int n with
|
|
| n ->
|
|
f n;
|
|
true
|
|
| exception _ ->
|
|
fprintf ppf
|
|
"Integer literal exceeds the range of representable \
|
|
integers for directive `%s'.@."
|
|
dir_name;
|
|
false)
|
|
| Directive_int _, Some { pdira_desc = Pdir_int (_, Some _); _} ->
|
|
fprintf ppf "Wrong integer literal for directive `%s'.@." dir_name;
|
|
false
|
|
| Directive_ident f, Some { pdira_desc = Pdir_ident lid; _} ->
|
|
f lid;
|
|
true
|
|
| Directive_bool f, Some { pdira_desc = Pdir_bool b; _} ->
|
|
f b;
|
|
true
|
|
| _ ->
|
|
fprintf ppf "Wrong type of argument for directive `%s'.@."
|
|
dir_name;
|
|
false))
|
|
|
|
let execute_phrase print_outcome ppf phr =
|
|
try execute_phrase print_outcome ppf phr
|
|
with exn ->
|
|
Warnings.reset_fatal ();
|
|
raise exn
|
|
|
|
(* Read and execute commands from a file, or from stdin if [name] is "". *)
|
|
|
|
let use_print_results = ref true
|
|
|
|
let preprocess_phrase ppf phr =
|
|
let phr =
|
|
match phr with
|
|
| Ptop_def str ->
|
|
let str =
|
|
Pparse.apply_rewriters_str ~restore:true ~tool_name:"ocaml" str
|
|
in
|
|
Ptop_def str
|
|
| phr -> phr
|
|
in
|
|
if !Clflags.dump_parsetree then Printast.top_phrase ppf phr;
|
|
if !Clflags.dump_source then Pprintast.top_phrase ppf phr;
|
|
phr
|
|
|
|
let use_channel ppf ~wrap_in_module ic name filename =
|
|
let lb = Lexing.from_channel ic in
|
|
Warnings.reset_fatal ();
|
|
Location.init lb filename;
|
|
(* Skip initial #! line if any *)
|
|
Lexer.skip_hash_bang lb;
|
|
protect_refs
|
|
[ R (Location.input_name, filename); R (Location.input_lexbuf, Some lb) ]
|
|
(fun () ->
|
|
try
|
|
List.iter
|
|
(fun ph ->
|
|
let ph = preprocess_phrase ppf ph in
|
|
if not (execute_phrase !use_print_results ppf ph) then raise Exit)
|
|
(if wrap_in_module then parse_mod_use_file name lb
|
|
else !parse_use_file lb);
|
|
true
|
|
with
|
|
| Exit -> false
|
|
| Sys.Break ->
|
|
fprintf ppf "Interrupted.@.";
|
|
false
|
|
| x ->
|
|
Location.report_exception ppf x;
|
|
false)
|
|
|
|
let use_output ppf command =
|
|
let fn = Filename.temp_file "ocaml" "_toploop.ml" in
|
|
Misc.try_finally
|
|
~always:(fun () -> try Sys.remove fn with Sys_error _ -> ())
|
|
(fun () ->
|
|
match
|
|
Printf.ksprintf Sys.command "%s > %s" command (Filename.quote fn)
|
|
with
|
|
| 0 ->
|
|
let ic = open_in_bin fn in
|
|
Misc.try_finally
|
|
~always:(fun () -> close_in ic)
|
|
(fun () ->
|
|
use_channel ppf ~wrap_in_module:false ic "" "(command-output)")
|
|
| n ->
|
|
fprintf ppf "Command exited with code %d.@." n;
|
|
false)
|
|
|
|
let use_file ppf ~wrap_in_module name =
|
|
match name with
|
|
| "" -> use_channel ppf ~wrap_in_module stdin name "(stdin)"
|
|
| _ -> (
|
|
match Load_path.find name with
|
|
| filename ->
|
|
let ic = open_in_bin filename in
|
|
Misc.try_finally
|
|
~always:(fun () -> close_in ic)
|
|
(fun () -> use_channel ppf ~wrap_in_module ic name filename)
|
|
| exception Not_found ->
|
|
fprintf ppf "Cannot find file %s.@." name;
|
|
false)
|
|
|
|
let mod_use_file ppf name = use_file ppf ~wrap_in_module:true name
|
|
|
|
let use_file ppf name = use_file ppf ~wrap_in_module:false name
|
|
|
|
let use_silently ppf name =
|
|
protect_refs [ R (use_print_results, false) ] (fun () -> use_file ppf name)
|
|
|
|
module Topdirs = struct
|
|
(* Toplevel directives *)
|
|
|
|
(* The standard output formatter *)
|
|
let std_out = ref std_formatter
|
|
|
|
(* Directive sections (used in #help) *)
|
|
let section_general = "General"
|
|
|
|
let section_run = "Loading code"
|
|
|
|
let section_env = "Environment queries"
|
|
|
|
let section_print = "Pretty-printing"
|
|
|
|
let section_trace = "Tracing"
|
|
|
|
let section_options = "Compiler options"
|
|
|
|
let section_undocumented = "Undocumented"
|
|
|
|
(* we will print the sections in the first list,
|
|
then all user-defined sections,
|
|
then the sections in the second list,
|
|
then all undocumented directives *)
|
|
let order_of_sections =
|
|
( [ section_general; section_run; section_env ],
|
|
[ section_print; section_trace; section_options; section_undocumented ] )
|
|
(* Do not forget to keep the directives synchronized with the manual in
|
|
manual/manual/cmds/top.etex *)
|
|
|
|
(* To quit *)
|
|
|
|
let dir_quit () = raise (Compenv.Exit_with_status 0)
|
|
|
|
let _ =
|
|
add_directive "quit" (Directive_none dir_quit)
|
|
{ section = section_general; doc = "Exit the toplevel." }
|
|
|
|
(* To add a directory to the load path *)
|
|
|
|
let dir_directory s =
|
|
let d = expand_directory Config.standard_library s in
|
|
Dll.add_path [ d ];
|
|
let dir = Load_path.Dir.create d in
|
|
Load_path.add dir;
|
|
toplevel_env :=
|
|
Stdlib.String.Set.fold
|
|
(fun name env ->
|
|
Env.add_persistent_structure (Ident.create_persistent name) env)
|
|
(Env.persistent_structures_of_dir dir)
|
|
!toplevel_env
|
|
|
|
let _ =
|
|
add_directive "directory" (Directive_string dir_directory)
|
|
{
|
|
section = section_run;
|
|
doc =
|
|
"Add the given directory to search path for source and compiled \
|
|
files.";
|
|
}
|
|
|
|
(* To remove a directory from the load path *)
|
|
let dir_remove_directory s =
|
|
let d = expand_directory Config.standard_library s in
|
|
let keep id =
|
|
match Load_path.find_uncap (Ident.name id ^ ".cmi") with
|
|
| exception Not_found -> true
|
|
| fn -> Filename.dirname fn <> d
|
|
in
|
|
toplevel_env := Env.filter_non_loaded_persistent keep !toplevel_env;
|
|
Load_path.remove_dir s;
|
|
Dll.remove_path [ d ]
|
|
|
|
let _ =
|
|
add_directive "remove_directory" (Directive_string dir_remove_directory)
|
|
{
|
|
section = section_run;
|
|
doc = "Remove the given directory from the search path.";
|
|
}
|
|
(* To change the current directory *)
|
|
|
|
let dir_cd s = Sys.chdir s
|
|
|
|
let _ =
|
|
add_directive "cd" (Directive_string dir_cd)
|
|
{ section = section_run; doc = "Change the current working directory." }
|
|
(* Load in-core a .cmo file *)
|
|
|
|
exception Load_failed
|
|
|
|
let check_consistency ppf filename cu =
|
|
try Env.import_crcs ~source:filename cu.cu_imports
|
|
with
|
|
| Persistent_env.Consistbl.Inconsistency
|
|
{ unit_name = name; inconsistent_source = user; original_source = auth }
|
|
->
|
|
fprintf ppf "@[<hv 0>The files %s@ and %s@ disagree over interface %s@]@."
|
|
user auth name;
|
|
raise Load_failed
|
|
|
|
let load_compunit ic filename ppf compunit =
|
|
check_consistency ppf filename compunit;
|
|
seek_in ic compunit.cu_pos;
|
|
let code_size = compunit.cu_codesize + 8 in
|
|
let code = LongString.create code_size in
|
|
LongString.input_bytes_into code ic compunit.cu_codesize;
|
|
LongString.set code compunit.cu_codesize (Char.chr Opcodes.opRETURN);
|
|
LongString.blit_string "\000\000\000\001\000\000\000" 0 code
|
|
(compunit.cu_codesize + 1) 7;
|
|
let initial_symtable = Symtable.current_state () in
|
|
Symtable.patch_object code compunit.cu_reloc;
|
|
Symtable.update_global_table ();
|
|
let events =
|
|
if compunit.cu_debug = 0 then [||]
|
|
else (
|
|
seek_in ic compunit.cu_debug;
|
|
[| input_value ic |])
|
|
in
|
|
try
|
|
may_trace := true;
|
|
let _bytecode, closure = Meta.reify_bytecode code events None in
|
|
ignore (closure ());
|
|
may_trace := false
|
|
with exn ->
|
|
record_backtrace ();
|
|
may_trace := false;
|
|
Symtable.restore_state initial_symtable;
|
|
print_exception_outcome ppf exn;
|
|
raise Load_failed
|
|
|
|
let rec load_file recursive ppf name =
|
|
let filename = try Some (Load_path.find name) with Not_found -> None in
|
|
match filename with
|
|
| None ->
|
|
fprintf ppf "Cannot find file %s.@." name;
|
|
false
|
|
| Some filename ->
|
|
let ic = open_in_bin filename in
|
|
Misc.try_finally
|
|
~always:(fun () -> close_in ic)
|
|
(fun () -> really_load_file recursive ppf name filename ic)
|
|
|
|
and really_load_file recursive ppf name filename ic =
|
|
let buffer =
|
|
really_input_string ic (String.length Config.cmo_magic_number)
|
|
in
|
|
try
|
|
if buffer = Config.cmo_magic_number then (
|
|
let compunit_pos = input_binary_int ic in
|
|
(* Go to descriptor *)
|
|
seek_in ic compunit_pos;
|
|
let cu : compilation_unit = input_value ic in
|
|
if recursive then
|
|
List.iter
|
|
(function
|
|
| Reloc_getglobal id, _ when not (Symtable.is_global_defined id)
|
|
-> (
|
|
let file = Ident.name id ^ ".cmo" in
|
|
match Load_path.find_uncap file with
|
|
| exception Not_found -> ()
|
|
| file ->
|
|
if not (load_file recursive ppf file) then
|
|
raise Load_failed)
|
|
| _ -> ())
|
|
cu.cu_reloc;
|
|
load_compunit ic filename ppf cu;
|
|
true)
|
|
else if buffer = Config.cma_magic_number then (
|
|
let toc_pos = input_binary_int ic in
|
|
(* Go to table of contents *)
|
|
seek_in ic toc_pos;
|
|
let lib = (input_value ic : library) in
|
|
List.iter
|
|
(fun dllib ->
|
|
let name = Dll.extract_dll_name dllib in
|
|
try Dll.open_dlls Dll.For_execution [ name ]
|
|
with Failure reason ->
|
|
fprintf ppf
|
|
"Cannot load required shared library %s.@.Reason: %s.@." name
|
|
reason;
|
|
raise Load_failed)
|
|
lib.lib_dllibs;
|
|
List.iter (load_compunit ic filename ppf) lib.lib_units;
|
|
true)
|
|
else (
|
|
fprintf ppf "File %s is not a bytecode object file.@." name;
|
|
false)
|
|
with Load_failed -> false
|
|
|
|
let dir_load ppf name = ignore (load_file false ppf name)
|
|
|
|
let _ =
|
|
add_directive "load"
|
|
(Directive_string (dir_load !std_out))
|
|
{
|
|
section = section_run;
|
|
doc = "Load in memory a bytecode object, produced by ocamlc.";
|
|
}
|
|
|
|
let dir_load_rec ppf name = ignore (load_file true ppf name)
|
|
|
|
let _ =
|
|
add_directive "load_rec"
|
|
(Directive_string (dir_load_rec !std_out))
|
|
{
|
|
section = section_run;
|
|
doc = "As #load, but loads dependencies recursively.";
|
|
}
|
|
|
|
let load_file = load_file false
|
|
|
|
(* Load commands from a file *)
|
|
|
|
let dir_use ppf name = ignore (use_file ppf name)
|
|
|
|
let dir_use_output ppf name = ignore (use_output ppf name)
|
|
|
|
let dir_mod_use ppf name = ignore (mod_use_file ppf name)
|
|
|
|
let _ =
|
|
add_directive "use"
|
|
(Directive_string (dir_use !std_out))
|
|
{
|
|
section = section_run;
|
|
doc = "Read, compile and execute source phrases from the given file.";
|
|
}
|
|
|
|
let _ =
|
|
add_directive "use_output"
|
|
(Directive_string (dir_use_output !std_out))
|
|
{
|
|
section = section_run;
|
|
doc =
|
|
"Execute a command and read, compile and execute source phrases from \
|
|
its output.";
|
|
}
|
|
|
|
let _ =
|
|
add_directive "mod_use"
|
|
(Directive_string (dir_mod_use !std_out))
|
|
{
|
|
section = section_run;
|
|
doc =
|
|
"Usage is identical to #use but #mod_use wraps the contents in a \
|
|
module.";
|
|
}
|
|
|
|
(* Install, remove a printer *)
|
|
|
|
let filter_arrow ty =
|
|
let ty = Ctype.expand_head !toplevel_env ty in
|
|
match ty.desc with
|
|
| Tarrow (lbl, l, r, _) when not (Btype.is_optional lbl) -> Some (l, r)
|
|
| _ -> None
|
|
|
|
let rec extract_last_arrow desc =
|
|
match filter_arrow desc with
|
|
| None -> raise (Ctype.Unify [])
|
|
| Some ((_, r) as res) -> (
|
|
try extract_last_arrow r with Ctype.Unify _ -> res)
|
|
|
|
let extract_target_type ty = fst (extract_last_arrow ty)
|
|
|
|
let extract_target_parameters ty =
|
|
let ty = extract_target_type ty |> Ctype.expand_head !toplevel_env in
|
|
match ty.desc with
|
|
| Tconstr (path, (_ :: _ as args), _)
|
|
when Ctype.all_distinct_vars !toplevel_env args ->
|
|
Some (path, args)
|
|
| _ -> None
|
|
|
|
let printer_type ppf typename =
|
|
let printer_type =
|
|
match
|
|
Env.find_type_by_name (Ldot (Lident "Topdirs", typename)) !toplevel_env
|
|
with
|
|
| path, _ -> path
|
|
| exception Not_found ->
|
|
fprintf ppf "Cannot find type Topdirs.%s.@." typename;
|
|
raise Exit
|
|
in
|
|
printer_type
|
|
|
|
let match_simple_printer_type desc printer_type =
|
|
Ctype.begin_def ();
|
|
let ty_arg = Ctype.newvar () in
|
|
Ctype.unify !toplevel_env
|
|
(Ctype.newconstr printer_type [ ty_arg ])
|
|
(Ctype.instance desc.val_type);
|
|
Ctype.end_def ();
|
|
Ctype.generalize ty_arg;
|
|
(ty_arg, None)
|
|
|
|
let match_generic_printer_type desc path args printer_type =
|
|
Ctype.begin_def ();
|
|
let args = List.map (fun _ -> Ctype.newvar ()) args in
|
|
let ty_target = Ctype.newty (Tconstr (path, args, ref Mnil)) in
|
|
let ty_args =
|
|
List.map (fun ty_var -> Ctype.newconstr printer_type [ ty_var ]) args
|
|
in
|
|
let ty_expected =
|
|
List.fold_right
|
|
(fun ty_arg ty ->
|
|
Ctype.newty (Tarrow (Asttypes.Nolabel, ty_arg, ty, Cunknown)))
|
|
ty_args
|
|
(Ctype.newconstr printer_type [ ty_target ])
|
|
in
|
|
Ctype.unify !toplevel_env ty_expected (Ctype.instance desc.val_type);
|
|
Ctype.end_def ();
|
|
Ctype.generalize ty_expected;
|
|
if not (Ctype.all_distinct_vars !toplevel_env args) then
|
|
raise (Ctype.Unify []);
|
|
(ty_expected, Some (path, ty_args))
|
|
|
|
let match_printer_type ppf desc =
|
|
let printer_type_new = printer_type ppf "printer_type_new" in
|
|
let printer_type_old = printer_type ppf "printer_type_old" in
|
|
try (match_simple_printer_type desc printer_type_new, false)
|
|
with Ctype.Unify _ -> (
|
|
try (match_simple_printer_type desc printer_type_old, true)
|
|
with Ctype.Unify _ as exn -> (
|
|
match extract_target_parameters desc.val_type with
|
|
| None -> raise exn
|
|
| Some (path, args) ->
|
|
(match_generic_printer_type desc path args printer_type_new, false)))
|
|
|
|
let find_printer_type ppf lid =
|
|
match Env.find_value_by_name lid !toplevel_env with
|
|
| path, desc -> (
|
|
match match_printer_type ppf desc with
|
|
| ty_arg, is_old_style -> (ty_arg, path, is_old_style)
|
|
| exception Ctype.Unify _ ->
|
|
fprintf ppf "%a has a wrong type for a printing function.@."
|
|
Printtyp.longident lid;
|
|
raise Exit)
|
|
| exception Not_found ->
|
|
fprintf ppf "Unbound value %a.@." Printtyp.longident lid;
|
|
raise Exit
|
|
|
|
let dir_install_printer ppf lid =
|
|
try
|
|
let (ty_arg, ty), path, is_old_style = find_printer_type ppf lid in
|
|
let v = eval_value_path !toplevel_env path in
|
|
match ty with
|
|
| None ->
|
|
let print_function =
|
|
if is_old_style then fun _formatter repr -> Obj.obj v (Obj.obj repr)
|
|
else fun formatter repr -> Obj.obj v formatter (Obj.obj repr)
|
|
in
|
|
install_printer path ty_arg print_function
|
|
| Some (ty_path, ty_args) ->
|
|
let rec build v = function
|
|
| [] ->
|
|
let print_function =
|
|
if is_old_style then fun _formatter repr ->
|
|
Obj.obj v (Obj.obj repr)
|
|
else fun formatter repr -> Obj.obj v formatter (Obj.obj repr)
|
|
in
|
|
Zero print_function
|
|
| _ :: args ->
|
|
Succ (fun fn -> build ((Obj.obj v : _ -> Obj.t) fn) args)
|
|
in
|
|
install_generic_printer' path ty_path (build v ty_args)
|
|
with Exit -> ()
|
|
|
|
let dir_remove_printer ppf lid =
|
|
try
|
|
let _ty_arg, path, _is_old_style = find_printer_type ppf lid in
|
|
try remove_printer path
|
|
with Not_found ->
|
|
fprintf ppf "No printer named %a.@." Printtyp.longident lid
|
|
with Exit -> ()
|
|
|
|
let _ =
|
|
add_directive "install_printer"
|
|
(Directive_ident (dir_install_printer !std_out))
|
|
{
|
|
section = section_print;
|
|
doc = "Registers a printer for values of a certain type.";
|
|
}
|
|
|
|
let _ =
|
|
add_directive "remove_printer"
|
|
(Directive_ident (dir_remove_printer !std_out))
|
|
{
|
|
section = section_print;
|
|
doc = "Remove the named function from the table of toplevel printers.";
|
|
}
|
|
|
|
(* The trace *)
|
|
|
|
external current_environment : unit -> Obj.t = "caml_get_current_environment"
|
|
|
|
let tracing_function_ptr =
|
|
get_code_pointer
|
|
(Obj.repr (fun arg -> Trace.print_trace (current_environment ()) arg))
|
|
|
|
let dir_trace ppf lid =
|
|
match Env.find_value_by_name lid !toplevel_env with
|
|
| path, desc -> (
|
|
(* Check if this is a primitive *)
|
|
match desc.val_kind with
|
|
| Val_prim _ ->
|
|
fprintf ppf "%a is an external function and cannot be traced.@."
|
|
Printtyp.longident lid
|
|
| _ ->
|
|
let clos = eval_value_path !toplevel_env path in
|
|
(* Nothing to do if it's not a closure *)
|
|
if
|
|
Obj.is_block clos
|
|
&& (Obj.tag clos = Obj.closure_tag || Obj.tag clos = Obj.infix_tag)
|
|
&&
|
|
match Ctype.(repr (expand_head !toplevel_env desc.val_type)) with
|
|
| { desc = Tarrow _; _ } -> true
|
|
| _ -> false
|
|
then (
|
|
match is_traced clos with
|
|
| Some opath ->
|
|
fprintf ppf "%a is already traced (under the name %a).@."
|
|
Printtyp.path path Printtyp.path opath
|
|
| None ->
|
|
(* Instrument the old closure *)
|
|
traced_functions :=
|
|
{
|
|
path;
|
|
closure = clos;
|
|
actual_code = get_code_pointer clos;
|
|
instrumented_fun =
|
|
instrument_closure !toplevel_env lid ppf desc.val_type;
|
|
}
|
|
:: !traced_functions;
|
|
(* Redirect the code field of the closure to point
|
|
to the instrumentation function *)
|
|
set_code_pointer clos tracing_function_ptr;
|
|
fprintf ppf "%a is now traced.@." Printtyp.longident lid)
|
|
else fprintf ppf "%a is not a function.@." Printtyp.longident lid)
|
|
| exception Not_found ->
|
|
fprintf ppf "Unbound value %a.@." Printtyp.longident lid
|
|
|
|
let dir_untrace ppf lid =
|
|
match Env.find_value_by_name lid !toplevel_env with
|
|
| path, _desc ->
|
|
let rec remove = function
|
|
| [] ->
|
|
fprintf ppf "%a was not traced.@." Printtyp.longident lid;
|
|
[]
|
|
| f :: rem ->
|
|
if Path.same f.path path then (
|
|
set_code_pointer f.closure f.actual_code;
|
|
fprintf ppf "%a is no longer traced.@." Printtyp.longident lid;
|
|
rem)
|
|
else f :: remove rem
|
|
in
|
|
traced_functions := remove !traced_functions
|
|
| exception Not_found ->
|
|
fprintf ppf "Unbound value %a.@." Printtyp.longident lid
|
|
|
|
let dir_untrace_all ppf () =
|
|
List.iter
|
|
(fun f ->
|
|
set_code_pointer f.closure f.actual_code;
|
|
fprintf ppf "%a is no longer traced.@." Printtyp.path f.path)
|
|
!traced_functions;
|
|
traced_functions := []
|
|
|
|
let parse_warnings ppf iserr s =
|
|
try Warnings.parse_options iserr s
|
|
with Arg.Bad err -> fprintf ppf "%s.@." err
|
|
|
|
(* Typing information *)
|
|
|
|
let trim_signature = function
|
|
| Mty_signature sg ->
|
|
Mty_signature
|
|
(List.map
|
|
(function
|
|
| Sig_module (id, pres, md, rs, priv) ->
|
|
let attribute =
|
|
Ast_helper.Attr.mk (Location.mknoloc "...")
|
|
(Parsetree.PStr [])
|
|
in
|
|
Sig_module
|
|
( id,
|
|
pres,
|
|
{ md with md_attributes = attribute :: md.md_attributes },
|
|
rs,
|
|
priv )
|
|
(*| Sig_modtype (id, Modtype_manifest mty) ->
|
|
Sig_modtype (id, Modtype_manifest (trim_modtype mty))*)
|
|
| item -> item)
|
|
sg)
|
|
| mty -> mty
|
|
|
|
let show_prim to_sig ppf lid =
|
|
let env = !toplevel_env in
|
|
let loc = Location.none in
|
|
try
|
|
let s =
|
|
match lid with
|
|
| Longident.Lident s -> s
|
|
| Longident.Ldot (_, s) -> s
|
|
| Longident.Lapply _ ->
|
|
fprintf ppf "Invalid path %a@." Printtyp.longident lid;
|
|
raise Exit
|
|
in
|
|
let id = Ident.create_persistent s in
|
|
let sg = to_sig env loc id lid in
|
|
Printtyp.wrap_printing_env ~error:false env (fun () ->
|
|
fprintf ppf "@[%a@]@." Printtyp.signature sg)
|
|
with
|
|
| Not_found -> fprintf ppf "@[Unknown element.@]@."
|
|
| Exit -> ()
|
|
|
|
let all_show_funs = ref []
|
|
|
|
let reg_show_prim name to_sig doc =
|
|
all_show_funs := to_sig :: !all_show_funs;
|
|
add_directive name
|
|
(Directive_ident (show_prim to_sig !std_out))
|
|
{ section = section_env; doc }
|
|
|
|
let () =
|
|
reg_show_prim "show_val"
|
|
(fun env loc id lid ->
|
|
let _path, desc = Env.lookup_value ~loc lid env in
|
|
[ Sig_value (id, desc, Exported) ])
|
|
"Print the signature of the corresponding value."
|
|
|
|
let () =
|
|
reg_show_prim "show_type"
|
|
(fun env loc id lid ->
|
|
let _path, desc = Env.lookup_type ~loc lid env in
|
|
[ Sig_type (id, desc, Trec_not, Exported) ])
|
|
"Print the signature of the corresponding type constructor."
|
|
|
|
(* Each registered show_prim function is called in turn
|
|
* and any output produced is sent to std_out.
|
|
* Two show_prim functions are needed for constructors,
|
|
* one for exception constructors and another for
|
|
* non-exception constructors (normal and extensible variants). *)
|
|
let is_exception_constructor env type_expr =
|
|
Ctype.equal env true [ type_expr ] [ Predef.type_exn ]
|
|
|
|
let is_extension_constructor = function
|
|
| Cstr_extension _ -> true
|
|
| _ -> false
|
|
|
|
let () =
|
|
(* This show_prim function will only show constructor types
|
|
* that are not also exception types. *)
|
|
reg_show_prim "show_constructor"
|
|
(fun env loc id lid ->
|
|
let desc = Env.lookup_constructor ~loc Env.Positive lid env in
|
|
if is_exception_constructor env desc.cstr_res then raise Not_found;
|
|
let path =
|
|
match Ctype.repr desc.cstr_res with
|
|
| { desc = Tconstr (path, _, _); _} -> path
|
|
| _ -> raise Not_found
|
|
in
|
|
let type_decl = Env.find_type path env in
|
|
if is_extension_constructor desc.cstr_tag then
|
|
let ret_type =
|
|
if desc.cstr_generalized then Some desc.cstr_res else None
|
|
in
|
|
let ext =
|
|
{
|
|
ext_type_path = path;
|
|
ext_type_params = type_decl.type_params;
|
|
ext_args = Cstr_tuple desc.cstr_args;
|
|
ext_ret_type = ret_type;
|
|
ext_private = Asttypes.Public;
|
|
ext_loc = desc.cstr_loc;
|
|
ext_attributes = desc.cstr_attributes;
|
|
ext_uid = desc.cstr_uid;
|
|
}
|
|
in
|
|
[ Sig_typext (id, ext, Text_first, Exported) ]
|
|
else
|
|
(* make up a fake Ident.t as type_decl : Types.type_declaration
|
|
* does not have an Ident.t yet. Ident.create_presistent is a
|
|
* good choice because it has no side-effects.
|
|
* *)
|
|
let type_id = Ident.create_persistent (Path.name path) in
|
|
[ Sig_type (type_id, type_decl, Trec_first, Exported) ])
|
|
"Print the signature of the corresponding value constructor."
|
|
|
|
let () =
|
|
reg_show_prim "show_exception"
|
|
(fun env loc id lid ->
|
|
let desc = Env.lookup_constructor ~loc Env.Positive lid env in
|
|
if not (is_exception_constructor env desc.cstr_res) then raise Not_found;
|
|
let ret_type =
|
|
if desc.cstr_generalized then Some Predef.type_exn else None
|
|
in
|
|
let ext =
|
|
{
|
|
ext_type_path = Predef.path_exn;
|
|
ext_type_params = [];
|
|
ext_args = Cstr_tuple desc.cstr_args;
|
|
ext_ret_type = ret_type;
|
|
ext_private = Asttypes.Public;
|
|
ext_loc = desc.cstr_loc;
|
|
ext_attributes = desc.cstr_attributes;
|
|
ext_uid = desc.cstr_uid;
|
|
}
|
|
in
|
|
[ Sig_typext (id, ext, Text_exception, Exported) ])
|
|
"Print the signature of the corresponding exception."
|
|
|
|
let () =
|
|
reg_show_prim "show_module"
|
|
(fun env loc id lid ->
|
|
let rec accum_aliases md acc =
|
|
let acc =
|
|
Sig_module
|
|
( id,
|
|
Mp_present,
|
|
{ md with md_type = trim_signature md.md_type },
|
|
Trec_not,
|
|
Exported )
|
|
:: acc
|
|
in
|
|
match md.md_type with
|
|
| Mty_alias path ->
|
|
let md = Env.find_module path env in
|
|
accum_aliases md acc
|
|
| Mty_ident _ | Mty_signature _ | Mty_functor _ -> List.rev acc
|
|
in
|
|
let _, md = Env.lookup_module ~loc lid env in
|
|
accum_aliases md [])
|
|
"Print the signature of the corresponding module."
|
|
|
|
let () =
|
|
reg_show_prim "show_module_type"
|
|
(fun env loc id lid ->
|
|
let _path, desc = Env.lookup_modtype ~loc lid env in
|
|
[ Sig_modtype (id, desc, Exported) ])
|
|
"Print the signature of the corresponding module type."
|
|
|
|
let () =
|
|
reg_show_prim "show_class"
|
|
(fun env loc id lid ->
|
|
let _path, desc = Env.lookup_class ~loc lid env in
|
|
[ Sig_class (id, desc, Trec_not, Exported) ])
|
|
"Print the signature of the corresponding class."
|
|
|
|
let () =
|
|
reg_show_prim "show_class_type"
|
|
(fun env loc id lid ->
|
|
let _path, desc = Env.lookup_cltype ~loc lid env in
|
|
[ Sig_class_type (id, desc, Trec_not, Exported) ])
|
|
"Print the signature of the corresponding class type."
|
|
|
|
let show env loc id lid =
|
|
let sg =
|
|
List.fold_left
|
|
(fun sg f -> try f env loc id lid @ sg with _ -> sg)
|
|
[] !all_show_funs
|
|
in
|
|
if sg = [] then raise Not_found else sg
|
|
|
|
let () =
|
|
add_directive "show"
|
|
(Directive_ident (show_prim show !std_out))
|
|
{
|
|
section = section_env;
|
|
doc =
|
|
"Print the signatures of components from any of the categories below.";
|
|
}
|
|
|
|
let _ =
|
|
add_directive "trace"
|
|
(Directive_ident (dir_trace !std_out))
|
|
{
|
|
section = section_trace;
|
|
doc = "All calls to the function named function-name will be traced.";
|
|
}
|
|
|
|
let _ =
|
|
add_directive "untrace"
|
|
(Directive_ident (dir_untrace !std_out))
|
|
{ section = section_trace; doc = "Stop tracing the given function." }
|
|
|
|
let _ =
|
|
add_directive "untrace_all"
|
|
(Directive_none (dir_untrace_all !std_out))
|
|
{
|
|
section = section_trace;
|
|
doc = "Stop tracing all functions traced so far.";
|
|
}
|
|
|
|
(* Control the printing of values *)
|
|
|
|
let _ =
|
|
add_directive "print_depth"
|
|
(Directive_int (fun n -> max_printer_depth := n))
|
|
{
|
|
section = section_print;
|
|
doc = "Limit the printing of values to a maximal depth of n.";
|
|
}
|
|
|
|
let _ =
|
|
add_directive "print_length"
|
|
(Directive_int (fun n -> max_printer_steps := n))
|
|
{
|
|
section = section_print;
|
|
doc = "Limit the number of value nodes printed to at most n.";
|
|
}
|
|
|
|
(* Set various compiler flags *)
|
|
|
|
let _ =
|
|
add_directive "labels"
|
|
(Directive_bool (fun b -> Clflags.classic := not b))
|
|
{
|
|
section = section_options;
|
|
doc = "Choose whether to ignore labels in function types.";
|
|
}
|
|
|
|
let _ =
|
|
add_directive "principal"
|
|
(Directive_bool (fun b -> Clflags.principal := b))
|
|
{
|
|
section = section_options;
|
|
doc = "Make sure that all types are derived in a principal way.";
|
|
}
|
|
|
|
let _ =
|
|
add_directive "rectypes"
|
|
(Directive_none (fun () -> Clflags.recursive_types := true))
|
|
{
|
|
section = section_options;
|
|
doc = "Allow arbitrary recursive types during type-checking.";
|
|
}
|
|
|
|
let _ =
|
|
add_directive "ppx"
|
|
(Directive_string (fun s -> Clflags.all_ppx := s :: !Clflags.all_ppx))
|
|
{
|
|
section = section_options;
|
|
doc =
|
|
"After parsing, pipe the abstract syntax tree through the \
|
|
preprocessor command.";
|
|
}
|
|
|
|
let _ =
|
|
add_directive "warnings"
|
|
(Directive_string (parse_warnings !std_out false))
|
|
{
|
|
section = section_options;
|
|
doc = "Enable or disable warnings according to the argument.";
|
|
}
|
|
|
|
let _ =
|
|
add_directive "warn_error"
|
|
(Directive_string (parse_warnings !std_out true))
|
|
{
|
|
section = section_options;
|
|
doc = "Treat as errors the warnings enabled by the argument.";
|
|
}
|
|
|
|
(* #help directive *)
|
|
|
|
let directive_sections () =
|
|
let sections = Hashtbl.create 10 in
|
|
let add_dir name dir =
|
|
let section, doc =
|
|
match Hashtbl.find directive_info_table name with
|
|
| { section; doc } -> (section, Some doc)
|
|
| exception Not_found -> ("Undocumented", None)
|
|
in
|
|
Hashtbl.replace sections section
|
|
((name, dir, doc)
|
|
:: (try Hashtbl.find sections section with Not_found -> []))
|
|
in
|
|
Hashtbl.iter add_dir directive_table;
|
|
let take_section section =
|
|
if not (Hashtbl.mem sections section) then (section, [])
|
|
else
|
|
let section_dirs =
|
|
Hashtbl.find sections section
|
|
|> List.sort (fun (n1, _, _) (n2, _, _) -> String.compare n1 n2)
|
|
in
|
|
Hashtbl.remove sections section;
|
|
(section, section_dirs)
|
|
in
|
|
let before, after = order_of_sections in
|
|
let sections_before = List.map take_section before in
|
|
let sections_after = List.map take_section after in
|
|
let sections_user =
|
|
Hashtbl.fold (fun section _ acc -> section :: acc) sections []
|
|
|> List.sort String.compare |> List.map take_section
|
|
in
|
|
sections_before @ sections_user @ sections_after
|
|
|
|
let print_directive ppf (name, directive, doc) =
|
|
let param =
|
|
match directive with
|
|
| Directive_none _ -> ""
|
|
| Directive_string _ -> " <str>"
|
|
| Directive_int _ -> " <int>"
|
|
| Directive_bool _ -> " <bool>"
|
|
| Directive_ident _ -> " <ident>"
|
|
in
|
|
match doc with
|
|
| None -> fprintf ppf "#%s%s@." name param
|
|
| Some doc ->
|
|
fprintf ppf "@[<hov 2>#%s%s@\n%a@]@." name param Format.pp_print_text
|
|
doc
|
|
|
|
let print_section ppf (section, directives) =
|
|
if directives <> [] then (
|
|
fprintf ppf "%30s%s@." "" section;
|
|
List.iter (print_directive ppf) directives;
|
|
fprintf ppf "@.")
|
|
|
|
let print_directives ppf () =
|
|
List.iter (print_section ppf) (directive_sections ())
|
|
|
|
let _ =
|
|
add_directive "help"
|
|
(Directive_none (print_directives !std_out))
|
|
{
|
|
section = section_general;
|
|
doc =
|
|
"Prints a list of all available directives, with corresponding \
|
|
argument type if appropriate.";
|
|
}
|
|
end
|
|
|
|
type evalenv = Format.formatter -> string -> unit
|
|
|
|
let eval lb ppf (text : string) =
|
|
Topdirs.std_out := ppf;
|
|
(read_interactive_input :=
|
|
fun buffer _ ->
|
|
Bytes.blit_string text 0 buffer 0 (String.length text);
|
|
Buffer.add_string phrase_buffer text;
|
|
(* Also populate the phrase buffer as new characters are added. *)
|
|
String.length text);
|
|
let snap = Btype.snapshot () in
|
|
try
|
|
Buffer.reset phrase_buffer;
|
|
(* Reset the phrase buffer, then flush the lexing buffer. *)
|
|
Lexing.flush_input lb;
|
|
(* calls read_interactive_input to fill buffer again *)
|
|
Location.reset ();
|
|
Warnings.reset_fatal ();
|
|
let phr = try !parse_toplevel_phrase lb with Exit -> raise PPerror in
|
|
let phr = preprocess_phrase ppf phr in
|
|
Env.reset_cache_toplevel ();
|
|
ignore (execute_phrase true ppf phr)
|
|
with
|
|
| End_of_file ->
|
|
F.epr "Topmain.eval End_of_file exception\n";
|
|
Btype.backtrack snap
|
|
| Sys.Break ->
|
|
F.epr "Topmain.eval Sys.Break exception\n";
|
|
F.pf ppf "Interrupted.@.";
|
|
Btype.backtrack snap
|
|
| PPerror ->
|
|
F.epr "Topmain.eval PPerror exception\n";
|
|
()
|
|
| x ->
|
|
F.epr "Topmain.eval unknown exception\n";
|
|
Location.report_exception ppf x;
|
|
Btype.backtrack snap
|
|
|
|
let preload_objects = ref [ (*"komm.cma"*) ]
|
|
|
|
let init ppf =
|
|
Topdirs.std_out := ppf;
|
|
Clflags.include_dirs :=
|
|
List.rev_append [ Sys.getcwd () ] !Clflags.include_dirs;
|
|
(* Topdirs.dir_directory ((Sys.getcwd ()) ^ "/topfind");*)
|
|
let extra_paths =
|
|
match Sys.getenv "OCAML_TOPLEVEL_PATH" with
|
|
| exception Not_found -> []
|
|
| s -> Misc.split_path_contents s
|
|
in
|
|
Clflags.include_dirs := List.rev_append extra_paths !Clflags.include_dirs;
|
|
Compenv.readenv ppf Before_args;
|
|
Compenv.readenv ppf Before_link;
|
|
Compmisc.read_clflags_from_env ();
|
|
set_paths ();
|
|
(try
|
|
let res =
|
|
List.for_all
|
|
(fun name ->
|
|
Topdirs.load_file ppf name)
|
|
(List.rev !preload_objects @ !Compenv.first_objfiles)
|
|
in
|
|
run_hooks Startup;
|
|
if not res then raise Exit
|
|
with Exit as x ->
|
|
Format.fprintf ppf "Topmain.init: Uncaught exception: %s\n"
|
|
(Printexc.to_string x));
|
|
Compmisc.init_path ();
|
|
Clflags.debug := true;
|
|
Location.formatter_for_warnings := ppf;
|
|
if not !Clflags.noversion then F.pf ppf "OCaml version %s@.@." Config.version;
|
|
(try initialize_toplevel_env ()
|
|
with (Env.Error _ | Typetexp.Error _) as exn ->
|
|
Location.report_exception ppf exn;
|
|
raise Exit);
|
|
let lb = Lexing.from_function (fun b l -> !read_interactive_input b l) in
|
|
Location.init lb "//toplevel//";
|
|
Location.input_name := "//toplevel//";
|
|
Location.input_lexbuf := Some lb;
|
|
Location.input_phrase_buffer := Some phrase_buffer;
|
|
Sys.catch_break true;
|
|
run_hooks After_setup;
|
|
eval lb
|