(**************************************************************************)
(*                                                                        *)
(*                                 OCaml                                  *)
(*                                                                        *)
(*           Jerome Vouillon, projet Cristal, INRIA Rocquencourt          *)
(*           OCaml port by John Malecki and Xavier Leroy                  *)
(*                                                                        *)
(*   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.          *)
(*                                                                        *)
(**************************************************************************)

(************************ Reading and executing commands ***************)

open Int64ops
open Format
open Instruct
open Unix
open Debugger_config
open Types
open Primitives
open Unix_tools
open Debugger_parser
open Parser_aux
open Debugger_lexer
open Input_handling
open Question
open Debugcom
open Program_loading
open Program_management
open Lexing
open Parameters
open Show_source
open Show_information
open Time_travel
open Events
open Symbols
open Source
open Breakpoints
open Checkpoints
open Frames
open Printval

module Lexer = Debugger_lexer

(** Instructions, variables and infos lists. **)
type dbg_instruction =
  { instr_name: string;                 (* Name of command *)
    instr_prio: bool;                   (* Has priority *)
    instr_action: formatter -> lexbuf -> unit;
                                        (* What to do *)
    instr_repeat: bool;                 (* Can be repeated *)
    instr_help: string }                (* Help message *)

let instruction_list = ref ([] : dbg_instruction list)

type dbg_variable =
  { var_name: string;                   (* Name of variable *)
    var_action: (lexbuf -> unit) * (formatter -> unit);
                                        (* Reading, writing fns *)
    var_help: string }                  (* Help message *)

let variable_list = ref ([] : dbg_variable list)

type dbg_info =
  { info_name: string;                  (* Name of info *)
    info_action: lexbuf -> unit;        (* What to do *)
    info_help: string }                 (* Help message *)

let info_list = ref ([] : dbg_info list)

(** Utilities. **)
let error text =
  eprintf "%s@." text;
  raise Toplevel

let check_not_windows feature =
  match Sys.os_type with
  | "Win32" ->
      error ("\'"^feature^"\' feature not supported on Windows")
  | _ ->
      ()

let eol =
  end_of_line Lexer.lexeme

let matching_elements list name instr =
  List.filter (function a -> isprefix instr (name a)) !list

let all_matching_instructions =
  matching_elements instruction_list (fun i -> i.instr_name)

(* itz 04-21-96 don't do priority completion in emacs mode *)
(* XL 25-02-97 why? I find it very confusing. *)

let matching_instructions instr =
  let all = all_matching_instructions instr in
  let prio = List.filter (fun i -> i.instr_prio) all in
  if prio = [] then all else prio

let matching_variables =
  matching_elements variable_list (fun v -> v.var_name)

let matching_infos =
  matching_elements info_list (fun i -> i.info_name)

let find_ident name matcher action alternative ppf lexbuf =
  match identifier_or_eol Lexer.lexeme lexbuf with
  | None -> alternative ppf
  | Some ident ->
      match matcher ident with
      | [] -> error ("Unknown " ^ name ^ ".")
      | [a] -> action a ppf lexbuf
      | _ -> error ("Ambiguous " ^ name ^ ".")

let find_variable action alternative ppf lexbuf =
  find_ident "variable name" matching_variables action alternative ppf lexbuf

let find_info action alternative ppf lexbuf =
  find_ident "info command" matching_infos action alternative ppf lexbuf

let add_breakpoint_at_pc pc =
  try
    new_breakpoint (any_event_at_pc pc)
  with
  | Not_found ->
    eprintf "Can\'t add breakpoint at pc %i:%i: no event there.@."
            pc.frag pc.pos;
    raise Toplevel

let add_breakpoint_after_pc pc =
  let rec try_add n =
    if n < 3 then begin
      try
        new_breakpoint (any_event_at_pc {pc with pos = pc.pos + n * 4})
      with
      | Not_found ->
        try_add (n+1)
    end else begin
      error
        "Can\'t add breakpoint at beginning of function: no event there"
    end
  in try_add 0

let module_of_longident id =
  match id with
  | Some x -> Some (String.concat "." (Longident.flatten x))
  | None -> None

let convert_module mdle =
  match mdle with
  | Some m ->
     (* Strip .ml extension if any, beware that mdle might be a module path *)
     let stripped =
       if Filename.check_suffix m ".ml" then Filename.chop_suffix m ".ml"
       else m
     in
     Unit_info.modulize stripped
  | None ->
      try (get_current_event ()).ev_ev.ev_module
      with Not_found -> error "Not in a module."

(** Toplevel. **)
let current_line = ref ""

let interprete_line ppf line =
  current_line := line;
  let lexbuf = Lexing.from_string line in
    try
      match identifier_or_eol Lexer.lexeme lexbuf with
      | Some x ->
          begin match matching_instructions x with
          | [] ->
              error "Unknown command."
          | [i] ->
              i.instr_action ppf lexbuf;
              resume_user_input ();
              i.instr_repeat
          | _ ->
              error "Ambiguous command."
          end
      | None ->
          resume_user_input ();
          false
    with
    | Parsing.Parse_error ->
        error "Syntax error."
    | Lexer.Int_overflow ->
      error "Integer overflow"

let line_loop ppf line_buffer =
  resume_user_input ();
  let previous_line = ref "" in
    try
      while true do
        if !loaded then
          History.add_current_time ();
        let new_line = string_trim (line line_buffer) in
          let line =
            if new_line <> "" then
              new_line
            else
              !previous_line
          in
            previous_line := "";
            if interprete_line ppf line && !interactif then
              previous_line := line
      done
    with
    | Exit ->
        ()
(*    | Sys_error s ->
        error ("System error: " ^ s) *)

(** Instructions. **)
let instr_cd _ppf lexbuf =
  let dir = argument_eol argument lexbuf in
    if ask_kill_program () then
      try
        Sys.chdir (expand_path dir)
      with
      | Sys_error s ->
          error s

let instr_shell _ppf lexbuf =
  let cmdarg = argument_list_eol argument lexbuf in
  let cmd = String.concat " " cmdarg in
  (* perhaps we should use $SHELL -c ? *)
  let err = Sys.command cmd in
  if (err != 0) then
    eprintf "Shell command %S failed with exit code %d\n%!" cmd err

let instr_env _ppf lexbuf =
  let cmdarg = argument_list_eol argument lexbuf in
  let cmdarg = string_trim (String.concat " " cmdarg) in
  if cmdarg <> "" then
    if ask_kill_program () then begin
      try
        let eqpos = String.index cmdarg '=' in
        if eqpos = 0 then raise Not_found;
        let name = String.sub cmdarg 0 eqpos in
        let value =
          String.sub cmdarg (eqpos + 1) (String.length cmdarg - eqpos - 1)
        in
        Debugger_config.environment :=
          (name, value) :: List.remove_assoc name !Debugger_config.environment
      with Not_found ->
        eprintf "Environment variable must be in name=value format\n%!"
    end
  else
    List.iter
      (fun (vvar, vval) -> printf "%s=%s\n%!" vvar vval)
      (List.rev !Debugger_config.environment)

let instr_pwd ppf lexbuf =
  eol lexbuf;
  fprintf ppf "%s@." (Sys.getcwd ())

let instr_dir ppf lexbuf =
  let new_directory = argument_list_eol argument lexbuf in
    if new_directory = [] then begin
      if yes_or_no "Reinitialize directory list" then begin
        Load_path.init ~auto_include:Compmisc.auto_include
          ~visible:!default_load_path ~hidden:[];
        Envaux.reset_cache ();
        Hashtbl.clear Debugger_config.load_path_for;
        flush_buffer_list ()
        end
      end
    else begin
      let new_directory' = List.rev new_directory in
      match new_directory' with
      | mdl :: for_keyw :: tl
        when String.lowercase_ascii for_keyw = "for" && List.length tl > 0 ->
          List.iter (function x -> add_path_for mdl (expand_path x)) tl
      | _ ->
          List.iter (function x -> add_path (expand_path x)) new_directory'
    end;
    let print_dirs ppf l = List.iter (function x -> fprintf ppf "@ %s" x) l in
    fprintf ppf "@[<2>Directories: %a@]@." print_dirs
      (Load_path.get_path_list ());
    Hashtbl.iter
      (fun mdl dirs ->
         fprintf ppf "@[<2>Source directories for %s: %a@]@." mdl print_dirs
                 dirs)
      Debugger_config.load_path_for

let instr_kill _ppf lexbuf =
  eol lexbuf;
  if not !loaded then error "The program is not being run.";
  if (yes_or_no "Kill the program being debugged") then begin
    kill_program ();
    show_no_point()
  end

let instr_pid ppf lexbuf =
  eol lexbuf;
  if not !loaded then error "The program is not being run.";
  fprintf ppf "@[%d@]@." !current_checkpoint.c_pid

let instr_run ppf lexbuf =
  eol lexbuf;
  ensure_loaded ();
  reset_named_values ();
  run ();
  show_current_event ppf

let instr_reverse ppf lexbuf =
  eol lexbuf;
  check_not_windows "reverse";
  ensure_loaded ();
  reset_named_values();
  back_run ();
  show_current_event ppf

let instr_step ppf lexbuf =
  let step_count =
    match opt_signed_int64_eol Lexer.lexeme lexbuf with
    | None -> _1
    | Some x -> x
  in
    ensure_loaded ();
    reset_named_values();
    step step_count;
    show_current_event ppf

let instr_back ppf lexbuf =
  let step_count =
    match opt_signed_int64_eol Lexer.lexeme lexbuf with
    | None -> _1
    | Some x -> x
  in
    check_not_windows "backstep";
    ensure_loaded ();
    reset_named_values();
    step (_0 -- step_count);
    show_current_event ppf

let instr_finish ppf lexbuf =
  eol lexbuf;
  ensure_loaded ();
  reset_named_values();
  finish ();
  show_current_event ppf

let instr_next ppf lexbuf =
  let step_count =
    match opt_integer_eol Lexer.lexeme lexbuf with
    | None -> 1
    | Some x -> x
  in
    ensure_loaded ();
    reset_named_values();
    next step_count;
    show_current_event ppf

let instr_start ppf lexbuf =
  eol lexbuf;
  check_not_windows "start";
  ensure_loaded ();
  reset_named_values();
  start ();
  show_current_event ppf

let instr_previous ppf lexbuf =
  let step_count =
    match opt_integer_eol Lexer.lexeme lexbuf with
    | None -> 1
    | Some x -> x
  in
    check_not_windows "previous";
    ensure_loaded ();
    reset_named_values();
    previous step_count;
    show_current_event ppf

let instr_goto ppf lexbuf =
  let time = int64_eol Lexer.lexeme lexbuf in
    ensure_loaded ();
    reset_named_values();
    go_to time;
    show_current_event ppf

let instr_quit _ =
  raise Exit

let print_variable_list ppf =
  let pr_vars ppf = List.iter (fun v -> fprintf ppf "%s@ " v.var_name) in
  fprintf ppf "List of variables: %a@." pr_vars !variable_list

let print_info_list ppf =
  let pr_infos ppf = List.iter (fun i -> fprintf ppf "%s@ " i.info_name)  in
  fprintf ppf "List of info commands: %a@." pr_infos !info_list

let instr_complete _ppf lexbuf =
  let ppf = Format.err_formatter in
  let rec print_list l =
    try
      eol lexbuf;
      List.iter (function i -> fprintf ppf "%s@." i) l
    with _ ->
      remove_file !user_channel
  and match_list lexbuf =
    match identifier_or_eol Lexer.lexeme lexbuf with
    | None ->
        List.map (fun i -> i.instr_name) !instruction_list
    | Some x ->
        match matching_instructions x with
        | [ {instr_name = ("set" | "show" as i_full)} ] ->
            if x = i_full then begin
              match identifier_or_eol Lexer.lexeme lexbuf with
              | Some ident ->
                  begin match matching_variables ident with
                  | [v] -> if v.var_name = ident then [] else [v.var_name]
                  | l   -> List.map (fun v -> v.var_name) l
                  end
              | None ->
                  List.map (fun v -> v.var_name) !variable_list
            end
            else [i_full]
        | [ {instr_name = "info"} ] ->
            if x = "info" then begin
              match identifier_or_eol Lexer.lexeme lexbuf with
              | Some ident ->
                  begin match matching_infos ident with
                  | [i] -> if i.info_name = ident then [] else [i.info_name]
                  | l   -> List.map (fun i -> i.info_name) l
                  end
              | None ->
                  List.map (fun i -> i.info_name) !info_list
            end
            else ["info"]
        | [ {instr_name = "help"} ] ->
            if x = "help" then match_list lexbuf else ["help"]
        | [ i ] ->
            if x = i.instr_name then [] else [i.instr_name]
        | l ->
            List.map (fun i -> i.instr_name) l
  in
    print_list(match_list lexbuf)

let instr_help ppf lexbuf =
  let pr_instrs ppf =
      List.iter (fun i -> fprintf ppf "%s@ " i.instr_name) in
  match identifier_or_eol Lexer.lexeme lexbuf with
  | Some x ->
      let print_help nm hlp =
        eol lexbuf;
        fprintf ppf "%s: %s@." nm hlp in
      begin match matching_instructions x with
      | [] ->
          eol lexbuf;
          fprintf ppf "No matching command.@."
      | [ {instr_name = "set"} ] ->
          find_variable
            (fun v _ _ ->
               print_help ("set " ^ v.var_name) ("set " ^ v.var_help))
            (fun ppf ->
               print_help "set" "set debugger variable.";
               print_variable_list ppf)
            ppf
            lexbuf
      | [ {instr_name = "show"} ] ->
          find_variable
            (fun v _ _ ->
               print_help ("show " ^ v.var_name) ("show " ^ v.var_help))
            (fun _v ->
               print_help "show" "display debugger variable.";
               print_variable_list ppf)
            ppf
            lexbuf
      | [ {instr_name = "info"} ] ->
          find_info
            (fun i _ _ -> print_help ("info " ^ i.info_name) i.info_help)
            (fun ppf ->
               print_help "info"
                 "display infos about the program being debugged.";
               print_info_list ppf)
            ppf
            lexbuf
      | [i] ->
          print_help i.instr_name i.instr_help
      | l ->
          eol lexbuf;
          fprintf ppf "Ambiguous command \"%s\": %a@." x pr_instrs l
      end
  | None ->
      fprintf ppf "List of commands: %a@." pr_instrs !instruction_list

(* Printing values *)

let print_expr depth ev env ppf expr =
  try
    let (v, ty) = Eval.expression ev env expr in
    print_named_value depth expr env v ppf ty
  with
  | Eval.Error msg ->
    Eval.report_error ppf msg;
    raise Toplevel

let env_of_event =
  function
    None    -> Env.empty
  | Some ev ->
      Envaux.env_from_summary ev.ev_ev.ev_typenv ev.ev_ev.ev_typsubst

let print_command depth ppf lexbuf =
  let exprs = expression_list_eol Lexer.lexeme lexbuf in
  ensure_loaded ();
  let env =
    try
      env_of_event !selected_event
    with
    | Envaux.Error msg ->
        Envaux.report_error ppf msg;
        raise Toplevel
  in
  List.iter (print_expr depth !selected_event env ppf) exprs

let instr_print ppf lexbuf = print_command !max_printer_depth ppf lexbuf

let instr_display ppf lexbuf = print_command 1 ppf lexbuf

let instr_address ppf lexbuf =
  let exprs = expression_list_eol Lexer.lexeme lexbuf in
  ensure_loaded ();
  let env =
    try
      env_of_event !selected_event
    with
    | Envaux.Error msg ->
        Envaux.report_error ppf msg;
        raise Toplevel
  in
  let print_addr expr =
    let (v, _ty) =
      try Eval.expression !selected_event env expr
      with Eval.Error msg ->
        Eval.report_error ppf msg;
        raise Toplevel
    in
    match Remote_value.pointer v with
    | "" -> fprintf ppf "[not a remote value]@."
    | s -> fprintf ppf "0x%s@." s
  in
  List.iter print_addr exprs

(* Loading of command files *)

let extract_filename arg =
  (* Allow enclosing filename in quotes *)
  let l = String.length arg in
  let pos1 = if l > 0 && arg.[0] = '\"' then 1 else 0 in
  let pos2 = if l > 0 && arg.[l-1] = '\"' then l-1 else l in
  String.sub arg pos1 (pos2 - pos1)

let instr_source ppf lexbuf =
  let file = extract_filename(argument_eol argument lexbuf)
  and old_state = !interactif
  and old_channel = !user_channel in
    let io_chan =
      try
        io_channel_of_descr
          (openfile (Load_path.find (expand_path file))
             [O_RDONLY] 0)
      with
      | Not_found -> error "Source file not found."
      | (Unix_error _) as x  -> Unix_tools.report_error x; raise Toplevel
    in
      interactif := false;
      user_channel := io_chan;
      let loop () =
        line_loop ppf (Lexing.from_function read_user_input)
      and finally () =
        stop_user_input ();
        close_io io_chan;
        interactif := old_state;
        user_channel := old_channel
      in
      Fun.protect ~finally loop

let instr_set =
  find_variable
    (fun {var_action = (funct, _)} _ppf lexbuf -> funct lexbuf)
    (function _ppf -> error "Argument required.")

let instr_show =
  find_variable
    (fun {var_action = (_, funct)} ppf lexbuf -> eol lexbuf; funct ppf)
    (function ppf ->
       List.iter
         (function {var_name = nm; var_action = (_, funct)} ->
              fprintf ppf "%s: " nm;
              funct ppf)
         !variable_list)

let instr_info =
  find_info
    (fun i _ppf lexbuf -> i.info_action lexbuf)
    (function _ppf ->
       error "\"info\" must be followed by the name of an info command.")

let instr_break ppf lexbuf =
  let argument = break_argument_eol Lexer.lexeme lexbuf in
    ensure_loaded ();
    match argument with
    |  BA_none ->                                (* break *)
        (match !selected_event with
         | Some ev ->
             new_breakpoint ev
         | None ->
             error "Can\'t add breakpoint at this point.")
    | BA_pc {frag; pos} ->                      (* break PC *)
        add_breakpoint_at_pc {frag; pos}
    | BA_function expr ->                       (* break FUNCTION *)
        let env =
          try
            env_of_event !selected_event
          with
          | Envaux.Error msg ->
              Envaux.report_error ppf msg;
              raise Toplevel
        in
        begin try
          let (v, ty) = Eval.expression !selected_event env expr in
          match get_desc ty with
          | Tarrow _ ->
              add_breakpoint_after_pc (Remote_value.closure_code v)
          | _ ->
              eprintf "Not a function.@.";
              raise Toplevel
        with
        | Eval.Error msg ->
            Eval.report_error ppf msg;
            raise Toplevel
        end
    | BA_pos1 (mdle, line, column) ->         (* break @ [MODULE] LINE [COL] *)
        let module_name = convert_module (module_of_longident mdle) in
        new_breakpoint
          (try
            let ev =  event_at_pos module_name 0 in
            let ev_pos =
              {Lexing.dummy_pos with
               pos_fname = (Events.get_pos ev.ev_ev).pos_fname} in
             let buffer =
               try get_buffer ev_pos module_name with
               | Not_found ->
                  eprintf "No source file for %s.@." module_name;
                  raise Toplevel
             in
             match column with
             | None ->
                 event_at_pos module_name (fst (pos_of_line buffer line))
             | Some col ->
                 event_near_pos module_name (point_of_coord buffer line col)
           with
           | Not_found -> (* event_at_pos / event_near pos *)
               eprintf "Can\'t find any event there.@.";
               raise Toplevel
           | Out_of_range -> (* pos_of_line / point_of_coord *)
               eprintf "Position out of range.@.";
               raise Toplevel)
    | BA_pos2 (mdle, position) ->             (* break @ [MODULE] # POSITION *)
        try
          new_breakpoint
            (event_near_pos (convert_module (module_of_longident mdle))
                            position)
        with
        | Not_found ->
            eprintf "Can\'t find any event there.@."

let instr_delete _ppf lexbuf =
  match integer_list_eol Lexer.lexeme lexbuf with
  | [] ->
      if breakpoints_count () <> 0 && yes_or_no "Delete all breakpoints"
      then remove_all_breakpoints ()
  | breakpoints ->
      List.iter
        (function x -> try remove_breakpoint x with | Not_found -> ())
        breakpoints

let instr_frame ppf lexbuf =
  let frame_number =
    match opt_integer_eol Lexer.lexeme lexbuf with
    | None -> !current_frame
    | Some x -> x
  in
    ensure_loaded ();
    try
      select_frame frame_number;
      show_current_frame ppf true
    with
    | Not_found ->
        error ("No frame number " ^ Int.to_string frame_number ^ ".")

let instr_backtrace ppf lexbuf =
  let number =
    match opt_signed_integer_eol Lexer.lexeme lexbuf with
    | None -> 0
    | Some x -> x in
  ensure_loaded ();
  match current_report() with
  | None | Some {rep_type = Exited | Uncaught_exc | Code_loaded _} -> ()
  | Some _ ->
      let frame_counter = ref 0 in
      let print_frame first_frame last_frame = function
      | None ->
          fprintf ppf
           "(Encountered a function with no debugging information)@.";
          false
      | Some event ->
          if !frame_counter >= first_frame then
            show_one_frame !frame_counter ppf event;
          incr frame_counter;
          if !frame_counter >= last_frame then begin
            fprintf ppf "(More frames follow)@."
          end;
          !frame_counter < last_frame in
      fprintf ppf "Backtrace:@.";
      if number = 0 then
        do_backtrace (print_frame 0 max_int)
      else if number > 0 then
        do_backtrace (print_frame 0 number)
      else begin
        let num_frames = stack_depth() in
        if num_frames < 0 then
          fprintf ppf
            "(Encountered a function with no debugging information)@."
        else
          do_backtrace (print_frame (num_frames + number) max_int)
      end

let instr_up ppf lexbuf =
  let offset =
    match opt_signed_integer_eol Lexer.lexeme lexbuf with
    | None -> 1
    | Some x -> x
  in
    ensure_loaded ();
    try
      select_frame (!current_frame + offset);
      show_current_frame ppf true
    with
    | Not_found -> error "No such frame."

let instr_down ppf lexbuf =
  let offset =
    match opt_signed_integer_eol Lexer.lexeme lexbuf with
    | None -> 1
    | Some x -> x
  in
    ensure_loaded ();
    try
      select_frame (!current_frame - offset);
      show_current_frame ppf true
    with
    | Not_found -> error "No such frame."

let instr_last ppf lexbuf =
  let count =
    match opt_signed_int64_eol Lexer.lexeme lexbuf with
    | None -> _1
    | Some x -> x
  in
    check_not_windows "last";
    reset_named_values();
    go_to (History.previous_time count);
    show_current_event ppf

let instr_list _ppf lexbuf =
  let (mo, beg, e) = list_arguments_eol Lexer.lexeme lexbuf in
    let (curr_mod, line, column) =
      try
        selected_point ()
      with
      | Not_found ->
          ("", -1, -1)
    in
      let mdle =
        match mo with
        | None -> curr_mod
        | _ -> convert_module (module_of_longident mo)
      in
      let pos = Lexing.dummy_pos in
      let buffer =
        try get_buffer pos mdle with
        | Not_found -> error ("No source file for " ^ mdle ^ ".") in
      let point =
        if column <> -1 then
          try
            (point_of_coord buffer line 1) + column
          with Out_of_range ->
            -1
        else
          -1 in
        let beginning =
          match beg with
          | None when (mo <> None) || (line = -1) ->
              1
          | None ->
              begin try
                Int.max 1 (line - 10)
              with Out_of_range ->
                1
              end
          | Some x -> x
        in
          let en =
            match e with
            | None -> beginning + 20
            | Some x -> x
          in
            if mdle = curr_mod then
              show_listing pos mdle beginning en point
                (current_event_is_before ())
            else
              show_listing pos mdle beginning en (-1) true

(** Variables. **)
let raw_variable kill name =
  (function lexbuf ->
     let argument = argument_eol argument lexbuf in
       if (not kill) || ask_kill_program () then name := argument),
  function ppf -> fprintf ppf "%s@." !name

let raw_line_variable kill name =
  (function lexbuf ->
     let argument = argument_eol line_argument lexbuf in
       if (not kill) || ask_kill_program () then name := argument),
  function ppf -> fprintf ppf "%s@." !name

let integer_variable kill min msg name =
  (function lexbuf ->
     let argument = integer_eol Lexer.lexeme lexbuf in
       if argument < min then print_endline msg
       else if (not kill) || ask_kill_program () then name := argument),
  function ppf -> fprintf ppf "%i@." !name

let int64_variable kill min msg name =
  (function lexbuf ->
     let argument = int64_eol Lexer.lexeme lexbuf in
       if argument < min then print_endline msg
       else if (not kill) || ask_kill_program () then name := argument),
  function ppf -> fprintf ppf "%Li@." !name

let boolean_variable kill name =
  (function lexbuf ->
     let argument =
       match identifier_eol Lexer.lexeme lexbuf with
       | "on" -> true
       | "of" | "off" -> false
       | _ -> error "Syntax error."
     in
       if (not kill) || ask_kill_program () then name := argument),
  function ppf -> fprintf ppf "%s@." (if !name then "on" else "off")

let path_variable kill name =
  (function lexbuf ->
       let argument = argument_eol argument lexbuf in
         if (not kill) || ask_kill_program () then
           name := make_absolute (expand_path argument)),
  function ppf -> fprintf ppf "%s@." !name

let loading_mode_variable ppf =
  (find_ident
     "loading mode"
     (matching_elements (ref loading_modes) fst)
     (fun (_, mode) _ppf lexbuf ->
        eol lexbuf; set_launching_function mode)
     (function _ppf -> error "Syntax error.")
     ppf),
  function ppf ->
    let rec find = function
      | [] -> ()
      | (name, funct) :: l ->
          if funct == !launching_func then fprintf ppf "%s" name else find l
    in
      find loading_modes;
      fprintf ppf "@."

let follow_fork_variable =
  (function lexbuf ->
     let mode =
       match identifier_eol Lexer.lexeme lexbuf with
       | "child" -> Fork_child
       | "parent" -> Fork_parent
       | _ -> error "Syntax error."
     in
       fork_mode := mode;
       if !loaded then update_follow_fork_mode ()),
  function ppf ->
    fprintf ppf "%s@."
      (match !fork_mode with
         Fork_child -> "child"
       | Fork_parent -> "parent")

(** Infos. **)

let pr_modules ppf mods =
 let pr_mods ppf = List.iter (function x -> fprintf ppf "%s@ " x) in
 fprintf ppf "Used modules: @.%a@?" pr_mods mods

let info_modules ppf lexbuf =
  eol lexbuf;
  ensure_loaded ();
  pr_modules ppf !modules
(********
  print_endline "Opened modules: ";
  if !opened_modules_names = [] then
    print_endline "(no module opened)."
  else
    (List.iter (function x -> print_string x;print_space) !opened_modules_names;
     print_newline ())
*********)

let info_checkpoints ppf lexbuf =
  eol lexbuf;
  if !checkpoints = [] then fprintf ppf "No checkpoint.@."
  else
    (if !debug_breakpoints then
       (prerr_endline "               Time   Pid Version";
        List.iter
          (function
             {c_time = time; c_pid = pid; c_breakpoint_version = version} ->
               Printf.printf "%19Ld %5d %d\n" time pid version)
          !checkpoints)
     else
       (print_endline "               Time   Pid";
        List.iter
          (function
             {c_time = time; c_pid = pid} ->
               Printf.printf "%19Ld %5d\n" time pid)
          !checkpoints))

let info_one_breakpoint ppf (num, ev) =
  fprintf ppf "%3d %d:%10d  %s@." num ev.ev_frag ev.ev_ev.ev_pos
          (Pos.get_desc ev)

let info_breakpoints ppf lexbuf =
  eol lexbuf;
  if !breakpoints = [] then fprintf ppf "No breakpoints.@."
  else begin
    fprintf ppf "Num    Address  Where@.";
    List.iter (info_one_breakpoint ppf) (List.rev !breakpoints);
  end


let info_events _ppf lexbuf =
  ensure_loaded ();
  let mdle =
    convert_module (module_of_longident (opt_longident_eol Lexer.lexeme lexbuf))
  in
    print_endline ("Module: " ^ mdle);
    print_endline "   Address  Characters        Kind      Repr.";
    let frag, events = events_in_module mdle in
    List.iter
      (function ev ->
        let start_char, end_char =
          try
            let buffer = get_buffer (Events.get_pos ev) ev.ev_module in
            (snd (start_and_cnum buffer ev.ev_loc.Location.loc_start)),
            (snd (start_and_cnum buffer ev.ev_loc.Location.loc_end))
          with _ ->
            ev.ev_loc.Location.loc_start.Lexing.pos_cnum,
            ev.ev_loc.Location.loc_end.Lexing.pos_cnum in
        Printf.printf
           "%d:%10d %6d-%-6d  %10s %10s\n"
           frag
           ev.ev_pos
           start_char
           end_char
           ((match ev.ev_kind with
               Event_before   -> "before"
             | Event_after _  -> "after"
             | Event_pseudo   -> "pseudo")
            ^
            (match ev.ev_info with
               Event_function -> "/fun"
             | Event_return _ -> "/ret"
             | Event_other    -> ""))
           (match ev.ev_repr with
              Event_none        -> ""
            | Event_parent _    -> "(repr)"
            | Event_child repr  -> Int.to_string !repr))
      events

(** User-defined printers **)

let instr_load_printer ppf lexbuf =
  let filename = extract_filename(argument_eol argument lexbuf) in
  try
    Loadprinter.loadfile ppf filename
  with Loadprinter.Error e ->
    Loadprinter.report_error ppf e; raise Toplevel

let instr_install_printer ppf lexbuf =
  let lid = longident_eol Lexer.lexeme lexbuf in
  try
    Loadprinter.install_printer ppf lid
  with Loadprinter.Error e ->
    Loadprinter.report_error ppf e; raise Toplevel

let instr_remove_printer ppf lexbuf =
  let lid = longident_eol Lexer.lexeme lexbuf in
  try
    Loadprinter.remove_printer lid
  with Loadprinter.Error e ->
    Loadprinter.report_error ppf e; raise Toplevel

(** Initialization. **)
let init ppf =
  instruction_list := [
     { instr_name = "cd"; instr_prio = false;
       instr_action = instr_cd; instr_repeat = true; instr_help =
"set working directory to DIR for debugger and program being debugged." };
     { instr_name = "complete"; instr_prio = false;
       instr_action = instr_complete; instr_repeat = false; instr_help =
"complete word at cursor according to context. Useful for Emacs." };
     { instr_name = "pwd"; instr_prio = false;
       instr_action = instr_pwd; instr_repeat = true; instr_help =
"print working directory." };
     { instr_name = "directory"; instr_prio = false;
       instr_action = instr_dir; instr_repeat = false; instr_help =
"add directory DIR to beginning of search path for source and\n\
interface files.\n\
Forget cached info on source file locations and line positions.\n\
With no argument, reset the search path." };
     { instr_name = "kill"; instr_prio = false;
       instr_action = instr_kill; instr_repeat = true; instr_help =
"kill the program being debugged." };
     { instr_name = "pid"; instr_prio = false;
       instr_action = instr_pid; instr_repeat = true; instr_help =
"print the process ID of the current active process." };
     { instr_name = "address"; instr_prio = false;
       instr_action = instr_address; instr_repeat = true; instr_help =
"print the raw address of a value." };
     { instr_name = "help"; instr_prio = false;
       instr_action = instr_help; instr_repeat = true; instr_help =
"print list of commands." };
     { instr_name = "quit"; instr_prio = false;
       instr_action = instr_quit; instr_repeat = false; instr_help =
"exit the debugger." };
     { instr_name = "shell"; instr_prio = false;
       instr_action = instr_shell; instr_repeat = true; instr_help =
"Execute a given COMMAND through the system shell." };
     { instr_name = "environment"; instr_prio = false;
       instr_action = instr_env; instr_repeat = false; instr_help =
"environment variable to give to program being debugged when it is started." };
      (* Displacements *)
     { instr_name = "run"; instr_prio = true;
       instr_action = instr_run; instr_repeat = true; instr_help =
"run the program from current position." };
     { instr_name = "reverse"; instr_prio = false;
       instr_action = instr_reverse; instr_repeat = true; instr_help =
"run the program backward from current position." };
     { instr_name = "step"; instr_prio = true;
       instr_action = instr_step; instr_repeat = true; instr_help =
"step program until it reaches the next event.\n\
Argument N means do this N times (or till program stops for another reason)." };
     { instr_name = "backstep"; instr_prio = true;
       instr_action = instr_back; instr_repeat = true; instr_help =
"step program backward until it reaches the previous event.\n\
Argument N means do this N times (or till program stops for another reason)." };
     { instr_name = "goto"; instr_prio = false;
       instr_action = instr_goto; instr_repeat = true; instr_help =
"go to the given time." };
     { instr_name = "finish"; instr_prio = true;
       instr_action = instr_finish; instr_repeat = true; instr_help =
"execute until topmost stack frame returns." };
     { instr_name = "next"; instr_prio = true;
       instr_action = instr_next; instr_repeat = true; instr_help =
"step program until it reaches the next event.\n\
Skip over function calls.\n\
Argument N means do this N times (or till program stops for another reason)." };
     { instr_name = "start"; instr_prio = false;
       instr_action = instr_start; instr_repeat = true; instr_help =
"execute backward until the current function is exited." };
     { instr_name = "previous"; instr_prio = false;
       instr_action = instr_previous; instr_repeat = true; instr_help =
"step program until it reaches the previous event.\n\
Skip over function calls.\n\
Argument N means do this N times (or till program stops for another reason)." };
     { instr_name = "print"; instr_prio = true;
       instr_action = instr_print; instr_repeat = true; instr_help =
"print value of expressions (deep printing)." };
     { instr_name = "display"; instr_prio = true;
       instr_action = instr_display; instr_repeat = true; instr_help =
"print value of expressions (shallow printing)." };
     { instr_name = "source"; instr_prio = false;
       instr_action = instr_source; instr_repeat = true; instr_help =
"read command from file FILE." };
     (* Breakpoints *)
     { instr_name = "break"; instr_prio = false;
       instr_action = instr_break; instr_repeat = false; instr_help =
{|Set breakpoint.
Syntax: break
        break function-name
        break @ [module] linenum
        break @ [module] linenum columnnum
        break @ [module] # characternum
        break frag:pc
        break pc|} };
     { instr_name = "delete"; instr_prio = false;
       instr_action = instr_delete; instr_repeat = false; instr_help =
"delete some breakpoints.\n\
Arguments are breakpoint numbers with spaces in between.\n\
To delete all breakpoints, give no argument." };
     { instr_name = "set"; instr_prio = false;
       instr_action = instr_set; instr_repeat = false; instr_help =
"--unused--" };
     { instr_name = "show"; instr_prio = false;
       instr_action = instr_show; instr_repeat = true; instr_help =
"--unused--" };
     { instr_name = "info"; instr_prio = false;
       instr_action = instr_info; instr_repeat = true; instr_help =
"--unused--" };
     (* Frames *)
     { instr_name = "frame"; instr_prio = false;
       instr_action = instr_frame; instr_repeat = true; instr_help =
"select and print a stack frame.\n\
With no argument, print the selected stack frame.\n\
An argument specifies the frame to select." };
     { instr_name = "backtrace"; instr_prio = false;
       instr_action = instr_backtrace; instr_repeat = true; instr_help =
"print backtrace of all stack frames, or innermost COUNT frames.\n\
With a negative argument, print outermost -COUNT frames." };
     { instr_name = "bt"; instr_prio = false;
       instr_action = instr_backtrace; instr_repeat = true; instr_help =
"print backtrace of all stack frames, or innermost COUNT frames.\n\
With a negative argument, print outermost -COUNT frames." };
     { instr_name = "up"; instr_prio = false;
       instr_action = instr_up; instr_repeat = true; instr_help =
"select and print stack frame that called this one.\n\
An argument says how many frames up to go." };
     { instr_name = "down"; instr_prio = false;
       instr_action = instr_down; instr_repeat = true; instr_help =
"select and print stack frame called by this one.\n\
An argument says how many frames down to go." };
     { instr_name = "last"; instr_prio = true;
       instr_action = instr_last; instr_repeat = true; instr_help =
"go back to previous time." };
     { instr_name = "list"; instr_prio = false;
       instr_action = instr_list; instr_repeat = true; instr_help =
"list the source code." };
     (* User-defined printers *)
     { instr_name = "load_printer"; instr_prio = false;
       instr_action = instr_load_printer; instr_repeat = false; instr_help =
"load in the debugger a .cmo or .cma file containing printing functions." };
     { instr_name = "install_printer"; instr_prio = false;
       instr_action = instr_install_printer; instr_repeat = false; instr_help =
"use the given function for printing values of its input type.\n\
The code for the function must have previously been loaded in the debugger\n\
using \"load_printer\"." };
     { instr_name = "remove_printer"; instr_prio = false;
       instr_action = instr_remove_printer; instr_repeat = false; instr_help =
"stop using the given function for printing values of its input type." }
];
  variable_list := [
    (* variable name, (writing, reading), help reading, help writing *)
     { var_name = "arguments";
       var_action = raw_line_variable true arguments;
       var_help =
"arguments to give program being debugged when it is started." };
     { var_name = "program";
       var_action = path_variable true program_name;
       var_help =
"name of program to be debugged." };
     { var_name = "loadingmode";
       var_action = loading_mode_variable ppf;
       var_help =
"mode of loading.\n\
It can be either:\n\
  direct: the program is directly called by the debugger.\n\
  runtime: the debugger execute `ocamlrun programname arguments\'.\n\
  manual: the program is not launched by the debugger,\n\
    but manually by the user." };
     { var_name = "processcount";
       var_action = integer_variable false 1 "Must be >= 1."
                                     checkpoint_max_count;
       var_help =
"maximum number of process to keep." };
     { var_name = "checkpoints";
       var_action = boolean_variable false make_checkpoints;
       var_help =
"whether to make checkpoints or not." };
     { var_name = "bigstep";
       var_action = int64_variable false _1 "Must be >= 1."
                                     checkpoint_big_step;
       var_help =
"step between checkpoints during long displacements." };
     { var_name = "smallstep";
       var_action = int64_variable false _1 "Must be >= 1."
                                     checkpoint_small_step;
       var_help =
"step between checkpoints during small displacements." };
     { var_name = "socket";
       var_action = raw_variable true socket_name;
       var_help =
"name of the socket used by communications debugger-runtime." };
     { var_name = "history";
       var_action = integer_variable false 0 "" history_size;
       var_help =
"history size." };
     { var_name = "print_depth";
       var_action = integer_variable false 1 "Must be at least 1"
                                     max_printer_depth;
       var_help =
"maximal depth for printing of values." };
     { var_name = "print_length";
       var_action = integer_variable false 1 "Must be at least 1"
                                     max_printer_steps;
       var_help =
"maximal number of value nodes printed." };
     { var_name = "follow_fork_mode";
       var_action = follow_fork_variable;
       var_help =
"process to follow after forking.\n\
It can be either :\n\
  child: the newly created process.\n\
  parent: the process that called fork.\n" };
     { var_name = "break_on_load";
       var_action = boolean_variable false break_on_load;
       var_help =
"whether to stop after loading new code (e.g. with Dynlink)." }];

  info_list :=
    (* info name, function, help *)
    [{ info_name = "modules";
       info_action = info_modules ppf;
       info_help = "list opened modules." };
     { info_name = "checkpoints";
       info_action = info_checkpoints ppf;
       info_help = "list checkpoints." };
     { info_name = "breakpoints";
       info_action = info_breakpoints ppf;
       info_help = "list breakpoints." };
     { info_name = "events";
       info_action = info_events ppf;
       info_help = "list events in MODULE (default is current module)." }]

let _ = init std_formatter
