(***********************************************************************) (* *) (* 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 Q Public License version 1.0. *) (* *) (***********************************************************************) open Lexing let absname = ref false (* This reference should be in Clflags, but it would create an additional dependency and make bootstrapping Camlp4 more difficult. *) type t = { loc_start: position; loc_end: position; loc_ghost: bool };; let in_file name = let loc = { pos_fname = name; pos_lnum = 1; pos_bol = 0; pos_cnum = -1; } in { loc_start = loc; loc_end = loc; loc_ghost = true } ;; let none = in_file "_none_";; let curr lexbuf = { loc_start = lexbuf.lex_start_p; loc_end = lexbuf.lex_curr_p; loc_ghost = false };; let init lexbuf fname = lexbuf.lex_curr_p <- { pos_fname = fname; pos_lnum = 1; pos_bol = 0; pos_cnum = 0; } ;; let symbol_rloc () = { loc_start = Parsing.symbol_start_pos (); loc_end = Parsing.symbol_end_pos (); loc_ghost = false; };; let symbol_gloc () = { loc_start = Parsing.symbol_start_pos (); loc_end = Parsing.symbol_end_pos (); loc_ghost = true; };; let rhs_loc n = { loc_start = Parsing.rhs_start_pos n; loc_end = Parsing.rhs_end_pos n; loc_ghost = false; };; let input_name = ref "_none_" let input_lexbuf = ref (None : lexbuf option) (* Terminal info *) let status = ref Terminfo.Uninitialised let num_loc_lines = ref 0 (* number of lines already printed after input *) (* Highlight the locations using standout mode. *) let highlight_terminfo ppf num_lines lb locs = Format.pp_print_flush ppf (); (* avoid mixing Format and normal output *) (* Char 0 is at offset -lb.lex_abs_pos in lb.lex_buffer. *) let pos0 = -lb.lex_abs_pos in (* Do nothing if the buffer does not contain the whole phrase. *) if pos0 < 0 then raise Exit; (* Count number of lines in phrase *) let lines = ref !num_loc_lines in for i = pos0 to lb.lex_buffer_len - 1 do if lb.lex_buffer.[i] = '\n' then incr lines done; (* If too many lines, give up *) if !lines >= num_lines - 2 then raise Exit; (* Move cursor up that number of lines *) flush stdout; Terminfo.backup !lines; (* Print the input, switching to standout for the location *) let bol = ref false in print_string "# "; for pos = 0 to lb.lex_buffer_len - pos0 - 1 do if !bol then (print_string " "; bol := false); if List.exists (fun loc -> pos = loc.loc_start.pos_cnum) locs then Terminfo.standout true; if List.exists (fun loc -> pos = loc.loc_end.pos_cnum) locs then Terminfo.standout false; let c = lb.lex_buffer.[pos + pos0] in print_char c; bol := (c = '\n') done; (* Make sure standout mode is over *) Terminfo.standout false; (* Position cursor back to original location *) Terminfo.resume !num_loc_lines; flush stdout (* Highlight the location by printing it again. *) let highlight_dumb ppf lb loc = (* Char 0 is at offset -lb.lex_abs_pos in lb.lex_buffer. *) let pos0 = -lb.lex_abs_pos in (* Do nothing if the buffer does not contain the whole phrase. *) if pos0 < 0 then raise Exit; let end_pos = lb.lex_buffer_len - pos0 - 1 in (* Determine line numbers for the start and end points *) let line_start = ref 0 and line_end = ref 0 in for pos = 0 to end_pos do if lb.lex_buffer.[pos + pos0] = '\n' then begin if loc.loc_start.pos_cnum > pos then incr line_start; if loc.loc_end.pos_cnum > pos then incr line_end; end done; (* Print character location (useful for Emacs) *) Format.fprintf ppf "Characters %i-%i:@." loc.loc_start.pos_cnum loc.loc_end.pos_cnum; (* Print the input, underlining the location *) Format.pp_print_string ppf " "; let line = ref 0 in let pos_at_bol = ref 0 in for pos = 0 to end_pos do match lb.lex_buffer.[pos + pos0] with | '\n' -> if !line = !line_start && !line = !line_end then begin (* loc is on one line: underline location *) Format.fprintf ppf "@. "; for _i = !pos_at_bol to loc.loc_start.pos_cnum - 1 do Format.pp_print_char ppf ' ' done; for _i = loc.loc_start.pos_cnum to loc.loc_end.pos_cnum - 1 do Format.pp_print_char ppf '^' done end; if !line >= !line_start && !line <= !line_end then begin Format.fprintf ppf "@."; if pos < loc.loc_end.pos_cnum then Format.pp_print_string ppf " " end; incr line; pos_at_bol := pos + 1 | '\r' -> () (* discard *) | c -> if !line = !line_start && !line = !line_end then (* loc is on one line: print whole line *) Format.pp_print_char ppf c else if !line = !line_start then (* first line of multiline loc: print a dot for each char before loc_start *) if pos < loc.loc_start.pos_cnum then Format.pp_print_char ppf '.' else Format.pp_print_char ppf c else if !line = !line_end then (* last line of multiline loc: print a dot for each char after loc_end, even whitespaces *) if pos < loc.loc_end.pos_cnum then Format.pp_print_char ppf c else Format.pp_print_char ppf '.' else if !line > !line_start && !line < !line_end then (* intermediate line of multiline loc: print whole line *) Format.pp_print_char ppf c done (* Highlight the location using one of the supported modes. *) let rec highlight_locations ppf locs = match !status with Terminfo.Uninitialised -> status := Terminfo.setup stdout; highlight_locations ppf locs | Terminfo.Bad_term -> begin match !input_lexbuf with None -> false | Some lb -> let norepeat = try Sys.getenv "TERM" = "norepeat" with Not_found -> false in if norepeat then false else let loc1 = List.hd locs in try highlight_dumb ppf lb loc1; true with Exit -> false end | Terminfo.Good_term num_lines -> begin match !input_lexbuf with None -> false | Some lb -> try highlight_terminfo ppf num_lines lb locs; true with Exit -> false end (* Print the location in some way or another *) open Format let absolute_path s = (* This function could go into Filename *) let open Filename in let s = if is_relative s then concat (Sys.getcwd ()) s else s in (* Now simplify . and .. components *) let rec aux s = let base = basename s in let dir = dirname s in if dir = s then dir else if base = current_dir_name then aux dir else if base = parent_dir_name then dirname (aux dir) else concat (aux dir) base in aux s let show_filename file = if !absname then absolute_path file else file let print_filename ppf file = Format.fprintf ppf "%s" (show_filename file) let reset () = num_loc_lines := 0 let (msg_file, msg_line, msg_chars, msg_to, msg_colon) = ("File \"", "\", line ", ", characters ", "-", ":") (* return file, line, char from the given position *) let get_pos_info pos = (pos.pos_fname, pos.pos_lnum, pos.pos_cnum - pos.pos_bol) ;; let print_loc ppf loc = let (file, line, startchar) = get_pos_info loc.loc_start in let endchar = loc.loc_end.pos_cnum - loc.loc_start.pos_cnum + startchar in if file = "//toplevel//" then begin if highlight_locations ppf [loc] then () else fprintf ppf "Characters %i-%i" loc.loc_start.pos_cnum loc.loc_end.pos_cnum end else begin fprintf ppf "%s%a%s%i" msg_file print_filename file msg_line line; if startchar >= 0 then fprintf ppf "%s%i%s%i" msg_chars startchar msg_to endchar end ;; let print ppf loc = if loc.loc_start.pos_fname = "//toplevel//" && highlight_locations ppf [loc] then () else fprintf ppf "%a%s@." print_loc loc msg_colon ;; let print_error ppf loc = print ppf loc; fprintf ppf "Error: "; ;; let print_error_cur_file ppf = print_error ppf (in_file !input_name);; let print_warning loc ppf w = if Warnings.is_active w then begin let printw ppf w = let n = Warnings.print ppf w in num_loc_lines := !num_loc_lines + n in print ppf loc; fprintf ppf "Warning %a@." printw w; pp_print_flush ppf (); incr num_loc_lines; end ;; let prerr_warning loc w = print_warning loc err_formatter w;; let echo_eof () = print_newline (); incr num_loc_lines type 'a loc = { txt : 'a; loc : t; } let mkloc txt loc = { txt ; loc } let mknoloc txt = mkloc txt none type error = { loc: t; msg: string; sub: error list; if_highlight: string; (* alternative message if locations are highlighted *) } let errorf ?(loc = none) ?(sub = []) ?(if_highlight = "") = Printf.ksprintf (fun msg -> {loc; msg; sub; if_highlight}) let error ?(loc = none) ?(sub = []) ?(if_highlight = "") msg = {loc; msg; sub; if_highlight} let error_of_exn : (exn -> error option) list ref = ref [] let register_error_of_exn f = error_of_exn := f :: !error_of_exn let error_of_exn exn = let rec loop = function | [] -> None | f :: rest -> match f exn with | Some _ as r -> r | None -> loop rest in loop !error_of_exn let rec report_error ppf ({loc; msg; sub; if_highlight} as err) = let highlighted = if if_highlight <> "" then let rec collect_locs locs {loc; sub; if_highlight; _} = List.fold_left collect_locs (loc :: locs) sub in let locs = collect_locs [] err in highlight_locations ppf locs else false in if highlighted then Format.pp_print_string ppf if_highlight else begin print ppf loc; Format.pp_print_string ppf msg; List.iter (fun err -> Format.fprintf ppf "@\n@[<2>%a@]" report_error err) sub end let error_of_printer loc print x = let buf = Buffer.create 64 in let ppf = Format.formatter_of_buffer buf in pp_print_string ppf "Error: "; print ppf x; pp_print_flush ppf (); let msg = Buffer.contents buf in errorf ~loc "%s" msg let error_of_printer_file print x = error_of_printer (in_file !input_name) print x let () = register_error_of_exn (function | Sys_error msg -> Some (errorf ~loc:(in_file !input_name) "Error: I/O error: %s" msg) | Warnings.Errors n -> Some (errorf ~loc:(in_file !input_name) "Error: Some fatal warnings were triggered (%d occurrences)" n) | _ -> None ) let report_exception ppf exn = match error_of_exn exn with | Some err -> fprintf ppf "@[%a@]@." report_error err | None -> raise exn