(**************************************************************************)
(*                                                                        *)
(*                                 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.          *)
(*                                                                        *)
(**************************************************************************)

(** Source code locations (ranges of positions), used in parsetree.

  {b Warning:} this module is unstable and part of
  {{!Compiler_libs}compiler-libs}.

*)

open Format

type t = Warnings.loc = {
  loc_start: Lexing.position;
  loc_end: Lexing.position;
  loc_ghost: bool;
  }
(** [t] represents a range of characters in the source code.

    loc_ghost=false whenever the AST described by the location can be parsed
    from the location. In all other cases, loc_ghost must be true. Most
    locations produced by the parser have loc_ghost=false.
    When loc_ghost=true, the location is usually a best effort approximation.

    This info is used by tools like merlin that want to relate source code with
    parsetrees or later asts. ocamlprof skips instrumentation of ghost nodes.

    Example: in `let f x = x`, we have:
    - a structure item at location "let f x = x"
    - a pattern "f" at location "f"
    - an expression "fun x -> x" at location "x = x" with loc_ghost=true
    - a pattern "x" at location "x"
    - an expression "x" at location "x"
    In this case, every node has loc_ghost=false, except the node "fun x -> x",
    since [Parser.expression (Lexing.from_string "x = x")] would fail to parse.
    By contrast, in `let f = fun x -> x`, every node has loc_ghost=false.

    Line directives can modify the filenames and line numbers arbitrarily,
    which is orthogonal to loc_ghost, which describes the range of characters
    from loc_start.pos_cnum to loc_end.pos_cnum in the parsed string.
 *)

(** Note on the use of Lexing.position in this module.
   If [pos_fname = ""], then use [!input_name] instead.
   If [pos_lnum = -1], then [pos_bol = 0]. Use [pos_cnum] and
     re-parse the file to get the line and character numbers.
   Else all fields are correct.
*)

val none : t
(** An arbitrary value of type [t]; describes an empty ghost range. *)

val is_none : t -> bool
(** True for [Location.none], false any other location *)

val in_file : string -> t
(** Return an empty ghost range located in a given file. *)

val init : Lexing.lexbuf -> string -> unit
(** Set the file name and line number of the [lexbuf] to be the start
    of the named file. *)

val curr : Lexing.lexbuf -> t
(** Get the location of the current token from the [lexbuf]. *)

val symbol_rloc: unit -> t
val symbol_gloc: unit -> t

(** [rhs_loc n] returns the location of the symbol at position [n], starting
  at 1, in the current parser rule. *)
val rhs_loc: int -> t

val rhs_interval: int -> int -> t

val get_pos_info: Lexing.position -> string * int * int
(** file, line, char *)

type 'a loc = {
  txt : 'a;
  loc : t;
}

val mknoloc : 'a -> 'a loc
val mkloc : 'a -> t -> 'a loc


(** {1 Input info} *)

val input_name: string ref
val input_lexbuf: Lexing.lexbuf option ref

(* This is used for reporting errors coming from the toplevel.

   When running a toplevel session (i.e. when [!input_name] is "//toplevel//"),
   [!input_phrase_buffer] should be [Some buf] where [buf] contains the last
   toplevel phrase. *)
val input_phrase_buffer: Buffer.t option ref


(** {1 Toplevel-specific functions} *)

val echo_eof: unit -> unit
val reset: unit -> unit


(** {1 Rewriting path } *)

val rewrite_absolute_path: string -> string
(** [rewrite_absolute_path path] rewrites [path] to honor the
    BUILD_PATH_PREFIX_MAP variable
    if it is set. It does not check whether [path] is absolute or not.
    The result is as follows:
    - If BUILD_PATH_PREFIX_MAP is not set, just return [path].
    - otherwise, rewrite using the mapping (and if there are no
      matching prefixes that will just return [path]).

    See
    {{: https://reproducible-builds.org/specs/build-path-prefix-map/ }
    the BUILD_PATH_PREFIX_MAP spec}
    *)

val rewrite_find_first_existing: string -> string option
(** [rewrite_find_first_existing path] uses a BUILD_PATH_PREFIX_MAP mapping
    and tries to find a source in mapping
    that maps to a result that exists in the file system.
    There are the following return values:
    - [None], means either
      {ul {- BUILD_PATH_PREFIX_MAP is not set and [path] does not exists, or}
          {- no source prefixes of [path] in the mapping were found,}}
    - [Some target], means [target] exists and either
      {ul {- BUILD_PATH_PREFIX_MAP is not set and [target] = [path], or}
          {- [target] is the first file (in priority
             order) that [path] mapped to that exists in the file system.}}
    - [Not_found] raised, means some source prefixes in the map
      were found that matched [path], but none of them existed
      in the file system. The caller should catch this and issue
      an appropriate error message.

    See
    {{: https://reproducible-builds.org/specs/build-path-prefix-map/ }
    the BUILD_PATH_PREFIX_MAP spec}
    *)

val rewrite_find_all_existing_dirs: string -> string list
(** [rewrite_find_all_existing_dirs dir] accumulates a list of existing
    directories, [dirs], that are the result of mapping a potentially
    abstract directory, [dir], over all the mapping pairs in the
    BUILD_PATH_PREFIX_MAP environment variable, if any. The list [dirs]
    will be in priority order (head as highest priority).

    The possible results are:
    - [[]], means either
      {ul {- BUILD_PATH_PREFIX_MAP is not set and [dir] is not an existing
      directory, or}
          {- if set, then there were no matching prefixes of [dir].}}
    - [Some dirs], means dirs are the directories found. Either
      {ul {- BUILD_PATH_PREFIX_MAP is not set and [dirs = [dir]], or}
          {- it was set and [dirs] are the mapped existing directories.}}
    - Not_found raised, means some source prefixes in the map
      were found that matched [dir], but none of mapping results
      were existing directories (possibly due to misconfiguration).
      The caller should catch this and issue an appropriate error
      message.

    See
    {{: https://reproducible-builds.org/specs/build-path-prefix-map/ }
    the BUILD_PATH_PREFIX_MAP spec}
    *)

val absolute_path: string -> string
 (** [absolute_path path] first makes an absolute path, [s] from [path],
     prepending the current working directory if [path] was relative.
     Then [s] is rewritten using [rewrite_absolute_path].
     Finally the result is normalized by eliminating instances of
     ['.'] or ['..']. *)

(** {1 Printing locations} *)

val show_filename: string -> string
    (** In -absname mode, return the absolute path for this filename.
        Otherwise, returns the filename unchanged. *)

val print_filename: formatter -> string -> unit
val print_loc: formatter -> t -> unit
val print_locs: formatter -> t list -> unit
val separate_new_message: formatter -> unit

module Doc: sig
  val separate_new_message: unit Format_doc.printer
  val filename: string Format_doc.printer
  val quoted_filename: string Format_doc.printer
  val loc: t Format_doc.printer
  val locs: t list Format_doc.printer
end

(** {1 Toplevel-specific location highlighting} *)

val highlight_terminfo:
  Lexing.lexbuf -> formatter -> t list -> unit


(** {1 Reporting errors and warnings} *)

(** {2 The type of reports and report printers} *)

type msg = Format_doc.t loc

val msg: ?loc:t -> ('a, Format_doc.formatter, unit, msg) format4 -> 'a

type report_kind =
  | Report_error
  | Report_warning of string
  | Report_warning_as_error of string
  | Report_alert of string
  | Report_alert_as_error of string

type report = {
  kind : report_kind;
  main : msg;
  sub : msg list;
  footnote: Format_doc.t option
}

type report_printer = {
  (* The entry point *)
  pp : report_printer ->
    Format.formatter -> report -> unit;

  pp_report_kind : report_printer -> report ->
    Format.formatter -> report_kind -> unit;
  pp_main_loc : report_printer -> report ->
    Format.formatter -> t -> unit;
  pp_main_txt : report_printer -> report ->
    Format.formatter -> Format_doc.t -> unit;
  pp_submsgs : report_printer -> report ->
    Format.formatter -> msg list -> unit;
  pp_submsg : report_printer -> report ->
    Format.formatter -> msg -> unit;
  pp_submsg_loc : report_printer -> report ->
    Format.formatter -> t -> unit;
  pp_submsg_txt : report_printer -> report ->
    Format.formatter -> Format_doc.t -> unit;
}
(** A printer for [report]s, defined using open-recursion.
    The goal is to make it easy to define new printers by reusing code from
    existing ones.
*)

(** {2 Report printers used in the compiler} *)

val batch_mode_printer: report_printer

val terminfo_toplevel_printer: Lexing.lexbuf -> report_printer

val best_toplevel_printer: unit -> report_printer
(** Detects the terminal capabilities and selects an adequate printer *)

(** {2 Printing a [report]} *)

val print_report: formatter -> report -> unit
(** Display an error or warning report. *)

val report_printer: (unit -> report_printer) ref
(** Hook for redefining the printer of reports.

    The hook is a [unit -> report_printer] and not simply a [report_printer]:
    this is useful so that it can detect the type of the output (a file, a
    terminal, ...) and select a printer accordingly. *)

val default_report_printer: unit -> report_printer
(** Original report printer for use in hooks. *)


(** {1 Reporting warnings} *)

(** {2 Converting a [Warnings.t] into a [report]} *)

val report_warning: t -> Warnings.t -> report option
(** [report_warning loc w] produces a report for the given warning [w], or
   [None] if the warning is not to be printed. *)

val warning_reporter: (t -> Warnings.t -> report option) ref
(** Hook for intercepting warnings. *)

val default_warning_reporter: t -> Warnings.t -> report option
(** Original warning reporter for use in hooks. *)

(** {2 Printing warnings} *)

val formatter_for_warnings : formatter ref

val print_warning: t -> formatter -> Warnings.t -> unit
(** Prints a warning. This is simply the composition of [report_warning] and
   [print_report]. *)

val prerr_warning: t -> Warnings.t -> unit
(** Same as [print_warning], but uses [!formatter_for_warnings] as output
   formatter. *)

(** {1 Reporting alerts} *)

(** {2 Converting an [Alert.t] into a [report]} *)

val report_alert: t -> Warnings.alert -> report option
(** [report_alert loc w] produces a report for the given alert [w], or
   [None] if the alert is not to be printed. *)

val alert_reporter: (t -> Warnings.alert -> report option) ref
(** Hook for intercepting alerts. *)

val default_alert_reporter: t -> Warnings.alert -> report option
(** Original alert reporter for use in hooks. *)

(** {2 Printing alerts} *)

val print_alert: t -> formatter -> Warnings.alert -> unit
(** Prints an alert. This is simply the composition of [report_alert] and
   [print_report]. *)

val prerr_alert: t -> Warnings.alert -> unit
(** Same as [print_alert], but uses [!formatter_for_warnings] as output
   formatter. *)

val deprecated: ?def:t -> ?use:t -> t -> string -> unit
(** Prints a deprecation alert. *)

val alert: ?def:t -> ?use:t -> kind:string -> t -> string -> unit
(** Prints an arbitrary alert. *)

val auto_include_alert: string -> unit
(** Prints an alert that -I +lib has been automatically added to the load
    path *)

val deprecated_script_alert: string -> unit
(** [deprecated_script_alert command] prints an alert that [command foo] has
    been deprecated in favour of [command ./foo] *)

(** {1 Reporting errors} *)

type error = report
(** An [error] is a [report] which [report_kind] must be [Report_error]. *)

type delayed_msg = unit -> Format_doc.t option

val error: ?loc:t -> ?sub:msg list -> ?footnote:delayed_msg -> string -> error

val errorf: ?loc:t -> ?sub:msg list -> ?footnote:delayed_msg ->
  ('a, Format_doc.formatter, unit, error) format4 -> 'a

val aligned_error_hint:
  ?loc:t -> ?sub:msg list -> ?footnote:delayed_msg ->
  ('a, Format_doc.formatter, unit, Format_doc.t option ->  error) format4 -> 'a
(** [aligned_error_hint ?loc ?sub ?footnote fmt ... aligned_hint] produces an
    error report where the potential [aligned_hint] message has been aligned
    with the main error message before being added to the list of submessages.*)

val error_of_printer: ?loc:t -> ?sub:msg list -> ?footnote:delayed_msg ->
  (Format_doc.formatter -> 'a -> unit) -> 'a -> error

val error_of_printer_file: (Format_doc.formatter -> 'a -> unit) -> 'a -> error


(** {1 Automatically reporting errors for raised exceptions} *)

val register_error_of_exn: (exn -> error option) -> unit
(** Each compiler module which defines a custom type of exception
    which can surface as a user-visible error should register
    a "printer" for this exception using [register_error_of_exn].
    The result of the printer is an [error] value containing
    a location, a message, and optionally sub-messages (each of them
    being located as well). *)

val error_of_exn: exn -> [ `Ok of error | `Already_displayed ] option

exception Error of error
(** Raising [Error e] signals an error [e]; the exception will be caught and the
   error will be printed. *)

exception Already_displayed_error
(** Raising [Already_displayed_error] signals an error which has already been
   printed. The exception will be caught, but nothing will be printed *)

val raise_errorf: ?loc:t -> ?sub:msg list -> ?footnote:delayed_msg ->
  ('a, Format_doc.formatter, unit, 'b) format4 -> 'a

val report_exception: formatter -> exn -> unit
(** Reraise the exception if it is unknown. *)
