1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441
|
(**************************************************************************)
(* *)
(* 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. *)
(* *)
(**************************************************************************)
open Format
include Topcommon
include Topeval
type input =
| Stdin
| File of string
| String of string
let use_print_results = ref true
let filename_of_input = function
| File name -> name
| Stdin | String _ -> ""
let use_lexbuf ppf ~wrap_in_module lb ~modpath ~filename =
Warnings.reset_fatal ();
Location.init lb filename;
(* Skip initial #! line if any *)
Lexer.skip_hash_bang lb;
Misc.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 modpath lb
else
!parse_use_file lb);
true
with
| Exit -> false
| Sys.Break -> fprintf ppf "Interrupted.@."; false
| x -> Location.report_exception ppf x; false)
(** [~modpath] is used to determine the module name when [wrap_in_module]
[~filepath] is the filesystem path to the input,
[~filename] is the name of the file that should be shown
to the user. It may differ from [filepath] when using a temporary file. *)
let use_file ppf ~wrap_in_module ~modpath ~filepath ~filename =
let source = In_channel.with_open_bin filepath In_channel.input_all in
let lexbuf = Lexing.from_string source in
use_lexbuf ppf ~wrap_in_module lexbuf ~modpath ~filename
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 ->
use_file ppf ~wrap_in_module:false ~modpath:""
~filepath:fn ~filename:"(command-output)"
| n ->
fprintf ppf "Command exited with code %d.@." n;
false)
let use_input ppf ~wrap_in_module input =
match input with
| Stdin ->
let lexbuf = Lexing.from_channel stdin in
use_lexbuf ppf ~wrap_in_module lexbuf ~modpath:"" ~filename:"(stdin)"
| String value ->
let lexbuf = Lexing.from_string value in
use_lexbuf ppf ~wrap_in_module lexbuf
~modpath:"" ~filename:"(command-line input)"
| File name ->
match Load_path.find name with
| filename ->
use_file ppf ~wrap_in_module ~modpath:name ~filename ~filepath:filename
| exception Not_found ->
fprintf ppf "Cannot find file %s.@." name;
false
let mod_use_input ppf input =
use_input ppf ~wrap_in_module:true input
let use_input ppf input =
use_input ppf ~wrap_in_module:false input
let use_file ppf name =
use_input ppf (File name)
let use_silently ppf input =
Misc.protect_refs
[ R (use_print_results, false) ]
(fun () -> use_input ppf input)
let load_file = load_file false
(* Execute a script. If [name] is "", read the script from stdin. *)
let run_script ppf name args =
Clflags.debug := true;
override_sys_argv args;
let filename = filename_of_input name in
Compmisc.init_path ~dir:(Filename.dirname filename) ();
(* Note: would use [Filename.abspath] here, if we had it. *)
Sys.interactive := false;
run_hooks After_setup;
let explicit_name =
match name with
| File name as filename -> (
(* Prevent use_silently from searching in the path. *)
if name <> "" && Filename.is_implicit name
then File (Filename.concat Filename.current_dir_name name)
else filename)
| (Stdin | String _) as x -> x
in
use_silently ppf explicit_name
(* 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;
Topeval.init ()
(* Split a PATH-style variable, Windows-style. Entries are separated by
semicolons. Sections of entries may be double-quoted (which allows
semicolons in filenames to be quoted). The double-quote characters are
stripped (i.e. [f"o"o = foo]).
The Windows behaviour is sparsely documented: the primary source is the
comment from 1989 at the top of env/getpath.cpp in the Universal C Runtime.
See also https://devblogs.microsoft.com/oldnewthing/20060929-06/?p=29533 *)
let split_path_win32 path =
(* Buffer for storing the current segment being scanned *)
let buf = Buffer.create 256 in
let get_contents () =
let s = Buffer.contents buf in
Buffer.clear buf;
s
in
let add_segment segment_begin i =
Buffer.add_substring buf path segment_begin (i - segment_begin)
in
let len = String.length path in
let[@tail_mod_cons] rec parse segment_begin terminator i =
if i >= len then
(* Done - return the last entry *)
[get_contents (add_segment segment_begin i)]
else
let ch = path.[i] in
(* terminator is either ';' or '"' *)
if ch = terminator then begin
add_segment segment_begin i;
if ch = ';' then
(* Return this entry and begin scanning the next *)
get_contents () :: parse (succ i) ';' (succ i)
else
(* Finished scanning '".."' so continue scanning this entry *)
parse (succ i) ';' (succ i)
end else if ch = '"' then begin
(* Encountered the beginning of a quoted segment *)
add_segment segment_begin i;
parse (succ i) '"' (succ i)
end else
parse segment_begin terminator (succ i)
in
parse 0 ';' 0
let split_path =
if Sys.win32 then
split_path_win32
else
String.split_on_char ':'
external windows_xdg_defaults : unit -> string list = "caml_xdg_defaults"
let find_ocamlinit () =
let ocamlinit = ".ocamlinit" in
(* 1. .ocamlinit in the current directory *)
if Sys.file_exists ocamlinit then Some ocamlinit else
let init_ml = Filename.concat "ocaml" "init.ml" in
let getenv var = match Sys.getenv_opt var with Some "" -> None | v -> v in
let is_absolute = Fun.negate Filename.is_relative in
let exists_in_dir ~file 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 windows_xdg_defaults = Lazy.from_fun windows_xdg_defaults in
(* 2. ocaml/init.ml under $XDG_CONFIG_HOME (or $HOME/.config on Unix, if
$XDG_CONFIG_HOME is unset, empty or not an absolute path) *)
let check_xdg_config_home () =
match getenv "XDG_CONFIG_HOME" with
| Some dir when is_absolute dir ->
exists_in_dir ~file:init_ml dir
| _ ->
let default =
if Sys.win32 then
(* The first entry of the list is FOLDERID_LocalAppData (exposed by
default in the process environment as %LOCALAPPDATA%) *)
match Lazy.force windows_xdg_defaults with
| dir::_ -> Some dir
| [] -> None
else
Option.map (fun dir -> Filename.concat dir ".config") (home_dir ())
in
Option.bind default (exists_in_dir ~file:init_ml)
in
(* 3. ocaml/init.ml under any of $XDG_CONFIG_DIRS (or /etc/xdg on Unix, or
%LOCALAPPDATA%, %APPDATA%, %PROGRAMDATA% on Windows) *)
let check_xdg_config_dirs () =
let dirs_from_env =
match getenv "XDG_CONFIG_DIRS" with
| Some entry -> List.filter is_absolute (split_path entry)
| None -> []
in
let search =
if dirs_from_env = [] then
if Sys.win32 then
(* There's a non-zero chance that a user of Cygwin, etc. sets
XDG_CONFIG_HOME for their Cygwin installation and then starts
native Windows `ocaml.exe` from within that installation. In this
scenario, XDG_CONFIG_HOME is very unlikely to be a valid path (as
Cygwin won't have translated it from Unix notation). To mitigate
this, the default value we take for XDG_CONFIG_DIRS on Windows
includes the default for XDG_CONFIG_HOME again. If the Cygwin user
has set both XDG_CONFIG_HOME and XDG_CONFIG_DIRS then we can't help
them! *)
Lazy.force windows_xdg_defaults
else
["/etc/xdg"]
else
dirs_from_env
in
List.find_map (exists_in_dir ~file:init_ml) search
in
(* 4. .ocamlinit in $HOME *)
let check_home () =
Option.bind (home_dir ()) (exists_in_dir ~file:ocamlinit)
in
List.find_map (fun f -> f ())
[check_xdg_config_home;
check_xdg_config_dirs;
check_home]
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 (File f) )
else fprintf ppf "Init file not found: \"%s\".@." f
| None ->
match find_ocamlinit () with
| None -> ()
| Some file -> ignore (use_silently ppf (File file))
(* The interactive loop *)
exception PPerror
let ends_with_lf lb =
let open Lexing in
Bytes.get lb.lex_buffer (lb.lex_buffer_len - 1) = '\n'
(* Without changing the state of [lb], try to see if it contains a token.
Return [EOF] if there is no token in [lb], a token if there is one,
or raise a lexer error as appropriate.
Print lexer warnings or not according to [print_warnings].
*)
let look_ahead ~print_warnings lb =
let shadow =
Lexing.{ lb with
refill_buff = (fun newlb -> newlb.lex_eof_reached <- true);
lex_buffer = Bytes.copy lb.lex_buffer;
lex_mem = Array.copy lb.lex_mem;
}
in
Misc.protect_refs [
R (Lexer.print_warnings, print_warnings);
Location.(R (report_printer, fun () -> batch_mode_printer));
] (fun () -> Lexer.token shadow)
;;
(* Refill the buffer until the next linefeed or end-of-file that is not
inside a comment and check that its contents can be ignored.
We do this by adding whole lines to the lexbuf until one of these
occurs:
- it contains no tokens and no unterminated comments
- it contains some token or unterminated string
- it contains a lexical error
*)
let is_blank_with_linefeed lb =
let open Lexing in
if Bytes.get lb.lex_buffer lb.lex_curr_pos = '\n' then
(* shortcut for the most usual case *)
true
else begin
let rec loop () =
if not (lb.lex_eof_reached || ends_with_lf lb) then begin
(* Make sure the buffer does not contain a truncated line. *)
lb.refill_buff lb;
loop ()
end else begin
(* Check for tokens in the lexbuf. We may have to
repeat this step, so don't print any warnings yet. *)
match look_ahead ~print_warnings:false lb with
| EOF -> true (* no tokens *)
| _ -> false (* some token *)
| exception Lexer.(Error ((Unterminated_comment _
| Unterminated_string_in_comment _), _)) ->
(* In this case we don't know whether there will be a token
before the next linefeed, so get more chars and continue. *)
Misc.protect_refs [ R (comment_prompt_override, true) ]
(fun () -> lb.refill_buff lb);
loop ()
| exception _ -> false (* syntax error *)
end
in
loop ()
end
(* Read and parse toplevel phrases, stop when a complete phrase has been
parsed and the lexbuf contains and end of line with optional whitespace
and comments. *)
let rec get_phrases ppf lb phrs =
match !parse_toplevel_phrase lb with
| phr ->
if is_blank_with_linefeed lb then begin
(* The lexbuf does not contain any tokens. We know it will be
flushed after the phrases are evaluated, so print warnings now. *)
ignore (look_ahead ~print_warnings:true lb);
List.rev (phr :: phrs)
end else
get_phrases ppf lb (phr :: phrs)
| exception Exit -> raise PPerror
| exception e -> Location.report_exception ppf e; []
(* Type, compile and execute a phrase. *)
let process_phrase ppf snap phr =
snap := Btype.snapshot ();
Warnings.reset_fatal ();
let phr = preprocess_phrase ppf phr in
Env.reset_cache_toplevel ();
ignore(execute_phrase true ppf phr)
(* Type, compile and execute a list of phrases, setting the report printer
to batch mode for all but the first one.
We have to use batch mode for reporting for two reasons:
1. we can't underline several parts of the input line(s) in place
2. the execution of the first phrase may mess up the line count so we
can't move the cursor back to the correct line
*)
let process_phrases ppf snap phrs =
match phrs with
| [] -> ()
| phr :: rest ->
process_phrase ppf snap phr;
if rest <> [] then begin
let process ph = Location.reset (); process_phrase ppf snap ph in
Misc.protect_refs
Location.[R (report_printer, fun () -> batch_mode_printer)]
(fun () -> List.iter process rest)
end
let loop ppf =
Misc.Style.setup !Clflags.color;
Clflags.debug := true;
Location.formatter_for_warnings := ppf;
if not !Clflags.noversion then
fprintf ppf "OCaml version %s%s%s@.Enter %a for help.@.@."
Config.version
(if Topeval.implementation_label = "" then "" else " - ")
Topeval.implementation_label
(Format_doc.compat Misc.Style.inline_code) "#help;;";
let lb = Lexing.from_function refill_lexbuf 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;
load_ocamlinit ppf;
while true do
let snap = ref (Btype.snapshot ()) in
try
Lexing.flush_input lb;
(* Reset the phrase buffer when we flush the lexing buffer. *)
Buffer.reset phrase_buffer;
Location.reset();
first_line := true;
let phrs = get_phrases ppf lb [] in
process_phrases ppf snap phrs
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
let preload_objects = ref []
let prepare ppf ?input () =
let dir =
Option.map (fun inp -> Filename.dirname (filename_of_input inp)) input in
Topcommon.set_paths ?dir ();
begin try
initialize_toplevel_env ()
with Env.Error _ | Typetexp.Error _ as exn ->
Location.report_exception ppf exn; raise (Compenv.Exit_with_status 2)
end;
try
let res =
let objects =
List.rev (!preload_objects @ !Compenv.first_objfiles)
in
List.for_all (Topeval.load_file false ppf) objects
in
Topcommon.run_hooks Topcommon.Startup;
res
with x ->
try Location.report_exception ppf x; false
with x ->
Format.fprintf ppf "Uncaught exception: %s\n" (Printexc.to_string x);
false
|