(**************************************************************************) (* *) (* OCaml *) (* *) (* Xavier Leroy, projet Cristal, INRIA Rocquencourt *) (* *) (* Copyright 1996 Institut National de Recherche en Informatique et *) (* en Automatique. *) (* *) (* All rights reserved. This file is distributed under the terms of *) (* the GNU Lesser General Public License version 2.1, with the *) (* special exception on linking described in the file LICENSE. *) (* *) (**************************************************************************) (* The interactive toplevel loop *) open Format open Config open Misc open Parsetree open Types open Typedtree open Outcometree open Ast_helper type res = Ok of Obj.t | Err of string type evaluation_outcome = Result of Obj.t | Exception of exn let _dummy = (Ok (Obj.magic 0), Err "") external ndl_run_toplevel: string -> string -> res = "caml_natdynlink_run_toplevel" let global_symbol id = let sym = Compilenv.symbol_for_global id in match Dynlink.unsafe_get_global_value ~bytecode_or_asm_symbol:sym with | None -> fatal_error ("Opttoploop.global_symbol " ^ (Ident.unique_name id)) | Some obj -> obj let need_symbol sym = Option.is_none (Dynlink.unsafe_get_global_value ~bytecode_or_asm_symbol:sym) let dll_run dll entry = match (try Result (Obj.magic (ndl_run_toplevel dll entry)) with exn -> Exception exn) with | Exception _ as r -> r | Result r -> match Obj.magic r with | Ok x -> Result x | Err s -> fatal_error ("Opttoploop.dll_run " ^ s) 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) let remembered = ref Ident.empty let rec remember phrase_name i = function | [] -> () | Sig_value (id, _, _) :: rest | Sig_module (id, _, _, _, _) :: rest | Sig_typext (id, _, _, _) :: rest | Sig_class (id, _, _, _) :: rest -> remembered := Ident.add id (phrase_name, i) !remembered; remember phrase_name (succ i) rest | _ :: rest -> remember phrase_name i rest let toplevel_value id = try Ident.find_same id !remembered with _ -> Misc.fatal_error @@ "Unknown ident: " ^ Ident.unique_name id let close_phrase lam = let open Lambda in Ident.Set.fold (fun id l -> let glb, pos = toplevel_value id in let glob = Lprim (Pfield pos, [Lprim (Pgetglobal glb, [], Loc_unknown)], Loc_unknown) in Llet(Strict, Pgenval, id, glob, l) ) (free_variables lam) lam let toplevel_value id = let glob, pos = if Config.flambda then toplevel_value id else Translmod.nat_toplevel_name id in (Obj.magic (global_symbol glob)).(pos) (* Return the value referred to by a path *) let rec eval_address = function | Env.Aident id -> if Ident.persistent id || Ident.global id then global_symbol id else toplevel_value id | Env.Adot(a, pos) -> Obj.field (eval_address a) 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 _ -> 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 phrase_seqid = ref 0 let phrase_name = ref "TOP" (* CR-soon trefis for mshinwell: copy/pasted from Optmain. Should it be shared or? mshinwell: It should be shared, but after 4.03. *) module Backend = struct (* See backend_intf.mli. *) let symbol_for_global' = Compilenv.symbol_for_global' let closure_symbol = Compilenv.closure_symbol let really_import_approx = Import_approx.really_import_approx let import_symbol = Import_approx.import_symbol let size_int = Arch.size_int let big_endian = Arch.big_endian let max_sensible_number_of_arguments = (* The "-1" is to allow for a potential closure environment parameter. *) Proc.max_arguments_for_tailcalls - 1 end let backend = (module Backend : Backend_intf.S) let load_lambda ppf ~module_ident ~required_globals lam size = if !Clflags.dump_rawlambda then fprintf ppf "%a@." Printlambda.lambda lam; let slam = Simplif.simplify_lambda lam in if !Clflags.dump_lambda then fprintf ppf "%a@." Printlambda.lambda slam; let dll = if !Clflags.keep_asm_file then !phrase_name ^ ext_dll else Filename.temp_file ("caml" ^ !phrase_name) ext_dll in let filename = Filename.chop_extension dll in let program = { Lambda. code = slam; main_module_block_size = size; module_ident; required_globals; } in let middle_end = if Config.flambda then Flambda_middle_end.lambda_to_clambda else Closure_middle_end.lambda_to_clambda in Asmgen.compile_implementation ~toplevel:need_symbol ~backend ~filename ~prefixname:filename ~middle_end ~ppf_dump:ppf program; Asmlink.call_linker_shared [filename ^ ext_obj] dll; Sys.remove (filename ^ ext_obj); let dll = if Filename.is_implicit dll then Filename.concat (Sys.getcwd ()) dll else dll in let res = dll_run dll !phrase_name in (try Sys.remove dll with Sys_error _ -> ()); (* note: under windows, cannot remove a loaded dll (should remember the handles, close them in at_exit, and then remove files) *) res (* 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 (toplevel_value id) val_type) | _ -> None ) (* The current typing environment for the toplevel *) let toplevel_env = ref Env.empty (* 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 (* The table of toplevel directives. Filled by functions from module topdirs. *) let directive_table = (Hashtbl.create 13 : (string, directive_fun) Hashtbl.t) (* Execute a toplevel phrase *) let execute_phrase print_outcome ppf phr = match phr with | Ptop_def sstr -> let oldenv = !toplevel_env in incr phrase_seqid; phrase_name := Printf.sprintf "TOP%i" !phrase_seqid; Compilenv.reset ?packname:None !phrase_name; Typecore.reset_delayed_checks (); let sstr, rewritten = match sstr with | [ { pstr_desc = Pstr_eval (e, attrs) ; pstr_loc = loc } ] | [ { pstr_desc = Pstr_value (Asttypes.Nonrecursive, [{ pvb_expr = e ; pvb_pat = { ppat_desc = Ppat_any ; _ } ; pvb_attributes = attrs ; _ }]) ; pstr_loc = loc } ] -> let pat = Ast_helper.Pat.var (Location.mknoloc "_$") in let vb = Ast_helper.Vb.mk ~loc ~attrs pat e in [ Ast_helper.Str.value ~loc Asttypes.Nonrecursive [vb] ], true | _ -> sstr, false in let (str, sg, names, newenv) = Typemod.type_toplevel_phrase oldenv sstr in if !Clflags.dump_typedtree then Printtyped.implementation ppf str; let sg' = Typemod.Signature_names.simplify newenv names sg in ignore (Includemod.signatures oldenv ~mark:Mark_positive sg sg'); Typecore.force_delayed_checks (); let module_ident, res, required_globals, size = if Config.flambda then let { Lambda.module_ident; main_module_block_size = size; required_globals; code = res } = Translmod.transl_implementation_flambda !phrase_name (str, Tcoerce_none) in remember module_ident 0 sg'; module_ident, close_phrase res, required_globals, size else let size, res = Translmod.transl_store_phrases !phrase_name str in Ident.create_persistent !phrase_name, res, Ident.Set.empty, size in Warnings.check_fatal (); begin try toplevel_env := newenv; let res = load_lambda ppf ~required_globals ~module_ident res size in let out_phr = match res with | Result _ -> if Config.flambda then (* CR-someday trefis: *) Env.register_import_as_opaque (Ident.name module_ident) else Compilenv.record_global_approx_toplevel (); if print_outcome then Printtyp.wrap_printing_env ~error:false oldenv (fun () -> match str.str_items with | [] -> Ophr_signature [] | _ -> if rewritten then match sg' with | [ Sig_value (id, vd, _) ] -> let outv = outval_of_value newenv (toplevel_value id) vd.val_type in let ty = Printtyp.tree_of_type_scheme vd.val_type in Ophr_eval (outv, ty) | _ -> assert false else 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; begin match out_phr with | Ophr_eval (_, _) | Ophr_signature _ -> true | Ophr_exception _ -> false end with x -> toplevel_env := oldenv; raise x end | 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 begin match d with | None -> fprintf ppf "Unknown directive `%s'.@." dir_name; 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)} -> begin 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 end | 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 end (* 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 Location.init lb filename; (* Skip initial #! line if any *) Lexer.skip_hash_bang lb; let success = protect_refs [ R (Location.input_name, filename) ] (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) in success 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) (* Reading function for interactive use *) let first_line = ref true let got_eof = ref false;; let read_input_default prompt buffer len = output_string stdout prompt; flush stdout; let i = ref 0 in try while true do if !i >= len then raise Exit; let c = input_char stdin in Bytes.set buffer !i c; incr i; if c = '\n' then raise Exit; done; (!i, false) with | End_of_file -> (!i, true) | Exit -> (!i, false) let read_interactive_input = ref read_input_default let refill_lexbuf buffer len = if !got_eof then (got_eof := false; 0) else begin let prompt = if !Clflags.noprompt then "" else if !first_line then "# " else if !Clflags.nopromptcont then "" else if Lexer.in_comment () then "* " else " " in first_line := false; let (len, eof) = !read_interactive_input prompt buffer len in if eof then begin Location.echo_eof (); if len > 0 then got_eof := true; len end else len end (* 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 _ = Sys.interactive := true; Compmisc.init_path (); Clflags.dlcode := true; () let find_ocamlinit () = let ocamlinit = ".ocamlinit" in if Sys.file_exists ocamlinit then Some ocamlinit else let getenv var = match Sys.getenv var with | exception Not_found -> None | "" -> None | v -> Some v in let exists_in_dir dir file = match dir with | None -> None | Some dir -> let file = Filename.concat dir file in if Sys.file_exists file then Some file else None in let home_dir () = getenv "HOME" in let config_dir () = if Sys.win32 then None else match getenv "XDG_CONFIG_HOME" with | Some _ as v -> v | None -> match home_dir () with | None -> None | Some dir -> Some (Filename.concat dir ".config") in let init_ml = Filename.concat "ocaml" "init.ml" in match exists_in_dir (config_dir ()) init_ml with | Some _ as v -> v | None -> exists_in_dir (home_dir ()) ocamlinit let load_ocamlinit ppf = if !Clflags.noinit then () else match !Clflags.init_file with | Some f -> if Sys.file_exists f then ignore (use_silently ppf f) else fprintf ppf "Init file not found: \"%s\".@." f | None -> match find_ocamlinit () with | None -> () | Some file -> ignore (use_silently ppf file) ;; 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 let initialize_toplevel_env () = toplevel_env := Compmisc.initial_env() (* The interactive loop *) exception PPerror let loop ppf = Location.formatter_for_warnings := ppf; if not !Clflags.noversion then fprintf ppf " OCaml version %s - native toplevel@.@." Config.version; initialize_toplevel_env (); let lb = Lexing.from_function refill_lexbuf in Location.init lb "//toplevel//"; Location.input_name := "//toplevel//"; Location.input_lexbuf := Some lb; Sys.catch_break true; run_hooks After_setup; load_ocamlinit ppf; while true do let snap = Btype.snapshot () in try Lexing.flush_input lb; Location.reset(); first_line := true; let phr = try !parse_toplevel_phrase lb with Exit -> raise PPerror in let phr = preprocess_phrase ppf phr in Env.reset_cache_toplevel (); if !Clflags.dump_parsetree then Printast.top_phrase ppf phr; if !Clflags.dump_source then Pprintast.top_phrase ppf phr; ignore(execute_phrase true ppf phr) with | End_of_file -> raise (Compenv.Exit_with_status 0) | Sys.Break -> fprintf ppf "Interrupted.@."; Btype.backtrack snap | PPerror -> () | x -> Location.report_exception ppf x; Btype.backtrack snap done external caml_sys_modify_argv : string array -> unit = "caml_sys_modify_argv" let override_sys_argv new_argv = caml_sys_modify_argv new_argv; Arg.current := 0 (* Execute a script. If [name] is "", read the script from stdin. *) let run_script ppf name args = override_sys_argv args; Compmisc.init_path ~dir:(Filename.dirname name) (); (* Note: would use [Filename.abspath] here, if we had it. *) toplevel_env := Compmisc.initial_env(); Sys.interactive := false; run_hooks After_setup; let explicit_name = (* Prevent use_silently from searching in the path. *) if Filename.is_implicit name then Filename.concat Filename.current_dir_name name else name in use_silently ppf explicit_name