messageReporting.ml 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. open Extlib_leftovers
  2. open Globals
  3. open Common
  4. open CompilationContext
  5. let resolve_source file l1 p1 l2 p2 =
  6. if l1 = l2 && p1 = p2 && l1 = 1 && p1 = 1 then []
  7. else begin
  8. let ch = open_in_bin file in
  9. let curline = ref 1 in
  10. let lines = ref [] in
  11. let rec loop p line =
  12. let inc i line =
  13. if (!curline >= l1) && (!curline <= l2) then lines := (!curline, line) :: !lines;
  14. incr curline;
  15. (i, "")
  16. in
  17. let input_char_or_done ch line =
  18. try input_char ch with End_of_file -> begin
  19. ignore(inc 0 line);
  20. raise End_of_file
  21. end
  22. in
  23. let read_char line = match input_char_or_done ch line with
  24. | '\n' -> inc 1 line
  25. | '\r' ->
  26. ignore(input_char_or_done ch line);
  27. inc 2 line
  28. | c -> begin
  29. let line = ref (line ^ (String.make 1 c)) in
  30. let rec skip n =
  31. if n > 0 then begin
  32. let c = input_char_or_done ch !line in
  33. line := !line ^ (String.make 1 c);
  34. skip (n - 1)
  35. end
  36. in
  37. let code = int_of_char c in
  38. if code < 0xC0 then ()
  39. else if code < 0xE0 then skip 1
  40. else if code < 0xF0 then skip 2
  41. else skip 3;
  42. (1, !line)
  43. end
  44. in
  45. let (delta, line) = read_char line in
  46. loop (p + delta) line
  47. in
  48. try loop 0 ""; with End_of_file -> close_in ch;
  49. List.rev !lines
  50. end
  51. let error_printer file line = Printf.sprintf "%s:%d:" file line
  52. type error_context = {
  53. absolute_positions : bool;
  54. mutable last_positions : pos IntMap.t;
  55. mutable max_lines : int IntMap.t;
  56. mutable gutter : int IntMap.t;
  57. mutable previous : (pos * MessageSeverity.t * int) option;
  58. }
  59. let create_error_context absolute_positions = {
  60. absolute_positions = absolute_positions;
  61. last_positions = IntMap.empty;
  62. max_lines = IntMap.empty;
  63. gutter = IntMap.empty;
  64. previous = None;
  65. }
  66. let compiler_pretty_message_string com ectx cm =
  67. match cm.cm_message with
  68. (* Filter some messages that don't add much when using this message renderer *)
  69. | "End of overload failure reasons" -> None
  70. | _ -> begin
  71. ectx.last_positions <- (IntMap.add cm.cm_depth cm.cm_pos ectx.last_positions);
  72. let is_null_pos = cm.cm_pos = null_pos || cm.cm_pos.pmin = -1 in
  73. let is_unknown_file f = f = "" || f = "?" in
  74. (* Extract informations from position *)
  75. let l1, p1, l2, p2, epos, lines =
  76. if is_null_pos then begin
  77. let epos = if is_unknown_file cm.cm_pos.pfile then "(unknown position)" else cm.cm_pos.pfile in
  78. (-1, -1, -1, -1, epos, [])
  79. end else try begin
  80. let l1, p1, l2, p2 = Lexer.get_pos_coords cm.cm_pos in
  81. let lines = resolve_source cm.cm_pos.pfile l1 p1 l2 p2 in
  82. let epos =
  83. if lines = [] then cm.cm_pos.pfile
  84. else if ectx.absolute_positions then TPrinting.Printer.s_pos cm.cm_pos
  85. else Lexer.get_error_pos error_printer cm.cm_pos
  86. in
  87. (l1, p1, l2, p2, epos, lines)
  88. end with Not_found | Sys_error _ ->
  89. (1, 1, 1, 1, cm.cm_pos.pfile, [])
  90. in
  91. (* If 4 lines or less, display all; if more, crop the middle *)
  92. let lines = match lines with
  93. | _ :: (_ :: (_ :: (_ :: []))) -> lines
  94. | hd :: (_ :: (_ :: (_ :: l))) ->
  95. let _,line = hd in
  96. let indent = ref 0 in
  97. let found = ref false in
  98. while (not !found) && (!indent < (String.length line - 1)) do
  99. found := not (Lexer.is_whitespace (String.unsafe_get line !indent));
  100. indent := !indent + 1
  101. done;
  102. [hd; (0, (String.make (!indent+1) ' ') ^ "[...]"); List.hd (List.rev l)]
  103. | _ -> lines
  104. in
  105. let parent_pos =
  106. if cm.cm_depth = 0 then null_pos
  107. else (try IntMap.find (cm.cm_depth-1) ectx.last_positions with Not_found -> null_pos)
  108. in
  109. let prev_pos,prev_sev,prev_nl = match ectx.previous with
  110. | None -> (None, None, 0)
  111. | Some (p, sev, depth) -> (Some p, Some sev, depth)
  112. in
  113. let sev_changed = prev_sev = None || Some cm.cm_severity <> prev_sev in
  114. let pos_changed = (prev_pos = None || cm.cm_pos <> Option.get prev_pos || (cm.cm_depth <> prev_nl && cm.cm_depth <> prev_nl + 1)) && (parent_pos = null_pos || cm.cm_pos <> parent_pos) in
  115. let file_changed = prev_pos = None || (pos_changed && match (cm.cm_pos.pfile, (Option.get prev_pos).pfile) with
  116. | (f1, f2) when (is_unknown_file f1) && (is_unknown_file f2) -> false
  117. | (f1, f2) -> f1 <> f2
  118. ) in
  119. let display_heading = cm.cm_depth = 0 || sev_changed || file_changed in
  120. let has_source = match lines with | [] -> false | _ -> true in
  121. let display_source = has_source && (cm.cm_depth = 0 || sev_changed || pos_changed) in
  122. let display_pos_marker = (not is_null_pos) && has_source && (cm.cm_depth = 0 || sev_changed || pos_changed) in
  123. let gutter_len = (try String.length (Printf.sprintf "%d" (IntMap.find cm.cm_depth ectx.max_lines)) with Not_found -> 0) + 2 in
  124. let no_color = Define.defined com.defines Define.MessageNoColor in
  125. let c_reset = if no_color then "" else "\x1b[0m" in
  126. let c_bold = if no_color then "" else "\x1b[1m" in
  127. let c_dim = if no_color then "" else "\x1b[2m" in
  128. let (c_sev, c_sev_bg) = if no_color then ("", "") else match cm.cm_severity with
  129. | MessageSeverity.Warning -> ("\x1b[33m", "\x1b[30;43m")
  130. | Information | Hint -> ("\x1b[34m", "\x1b[30;44m")
  131. | Error -> ("\x1b[31m", "\x1b[30;41m")
  132. in
  133. let sev_label = if cm.cm_depth > 0 then " -> " else Printf.sprintf
  134. (if no_color then "[%s]" else " %s ")
  135. (match cm.cm_severity with
  136. | MessageSeverity.Warning -> "WARNING"
  137. | Information -> "INFO"
  138. | Hint -> "HINT"
  139. | Error -> "ERROR"
  140. ) in
  141. let out = ref "" in
  142. if display_heading then
  143. out := Printf.sprintf "%s%s%s\n\n"
  144. (* Severity heading *)
  145. (c_sev_bg ^ sev_label ^ c_reset ^ " ")
  146. (* Macro context indicator *)
  147. (if cm.cm_from_macro then c_sev ^ "(macro) " ^ c_reset else "")
  148. (* File + line pointer *)
  149. epos;
  150. (* Macros can send all sorts of bad positions; avoid failing too hard *)
  151. let safe_sub s pos len =
  152. if len < 0 then ""
  153. else
  154. let pos = if pos < 0 then 0 else pos in
  155. let slen = String.length s in
  156. if pos >= slen then ""
  157. else
  158. let len = if (pos + len) > slen then slen - pos else len in
  159. try String.sub s pos len with
  160. (* Should not happen anymore, but still better than a crash if I missed some case... *)
  161. | Invalid_argument _ -> (Printf.sprintf "[%s;%i;%i]" s pos len)
  162. in
  163. (* Error source *)
  164. if display_source then out := List.fold_left (fun out (l, line) ->
  165. let nb_len = String.length (string_of_int l) in
  166. let gutter = gutter_len - nb_len - 1 in
  167. (* Replace tabs with 1 space to avoid column misalignments *)
  168. let line = String.concat " " (ExtString.String.nsplit line "\t") in
  169. let len = String.length line in
  170. out ^ Printf.sprintf "%s%s | %s\n"
  171. (* left-padded line number *)
  172. (if gutter < 1 then "" else String.make gutter ' ')
  173. (if l = 0 then "-" else Printf.sprintf "%d" l)
  174. (* Source code at that line *)
  175. (
  176. if l = 0 then
  177. c_dim ^ line ^ c_reset
  178. else if l1 = l2 then
  179. (if p1 > 1 then c_dim ^ (safe_sub line 0 (p1-1)) else "")
  180. ^ c_reset ^ c_bold ^ (safe_sub line (p1-1) (p2-p1))
  181. ^ c_reset ^ c_dim ^ (safe_sub line (p2-1) (len - p2 + 1))
  182. ^ c_reset
  183. else begin
  184. (if (l = l1) then
  185. c_dim ^ (safe_sub line 0 (p1-1))
  186. ^ c_reset ^ c_bold ^ (safe_sub line (p1-1) (len-p1+1))
  187. ^ c_reset
  188. else if (l = l2) then
  189. c_bold ^ (safe_sub line 0 (p2-1))
  190. ^ c_reset ^ c_dim ^ (safe_sub line (p2-1) (len-p2+1))
  191. ^ c_reset
  192. else c_bold ^ line ^ c_reset)
  193. end
  194. )
  195. ) !out lines;
  196. (* Error position marker *)
  197. if display_pos_marker then
  198. out := Printf.sprintf "%s%s|%s\n"
  199. !out
  200. (String.make gutter_len ' ')
  201. (if l1 = l2 then String.make p1 ' ' ^ c_sev ^ String.make (if p1 = p2 then 1 else p2-p1) '^' ^ c_reset else "");
  202. (* Error message *)
  203. out := List.fold_left (fun out str -> Printf.sprintf "%s%s| %s\n"
  204. out
  205. (String.make gutter_len ' ')
  206. (* Remove "... " prefix *)
  207. (if (ExtString.String.starts_with str "... ") then String.sub str 4 ((String.length str) - 4) else str)
  208. ) !out (ExtString.String.nsplit cm.cm_message "\n");
  209. ectx.previous <- Some ((if is_null_pos then null_pos else cm.cm_pos), cm.cm_severity, cm.cm_depth);
  210. ectx.gutter <- (IntMap.add cm.cm_depth gutter_len ectx.gutter);
  211. (* Indent sub errors *)
  212. let rec indent ?(acc=0) depth =
  213. if depth = 0 then acc
  214. else indent ~acc:(acc + try IntMap.find (depth-1) ectx.gutter with Not_found -> 3) (depth-1)
  215. in
  216. Some (
  217. if cm.cm_depth > 0 then String.concat "\n" (List.map (fun str -> match str with
  218. | "" -> ""
  219. | _ -> (String.make (indent cm.cm_depth) ' ') ^ str
  220. ) (ExtString.String.nsplit !out "\n"))
  221. else !out
  222. )
  223. end
  224. let compiler_message_string ectx cm =
  225. let str = match cm.cm_severity with
  226. | MessageSeverity.Warning -> "Warning : " ^ cm.cm_message
  227. | Information | Error | Hint -> cm.cm_message
  228. in
  229. if cm.cm_pos = null_pos then
  230. Some str
  231. else begin
  232. let epos =
  233. if ectx.absolute_positions then TPrinting.Printer.s_pos cm.cm_pos
  234. else Lexer.get_error_pos error_printer cm.cm_pos
  235. in
  236. let str =
  237. let lines =
  238. match (ExtString.String.nsplit str "\n") with
  239. | first :: rest -> first :: List.map Error.compl_msg rest
  240. | l -> l
  241. in
  242. String.concat ("\n" ^ epos ^ " : ") lines
  243. in
  244. Some (Printf.sprintf "%s : %s" epos str)
  245. end
  246. let compiler_indented_message_string ectx cm =
  247. match cm.cm_message with
  248. (* Filter some messages that don't add much when using this message renderer *)
  249. | "End of overload failure reasons" -> None
  250. | _ ->
  251. let str = match cm.cm_severity with
  252. | MessageSeverity.Warning -> "Warning : " ^ cm.cm_message
  253. | Information -> "Info : " ^ cm.cm_message
  254. | Error | Hint -> cm.cm_message
  255. in
  256. if cm.cm_pos = null_pos then
  257. Some str
  258. else begin
  259. let epos =
  260. if ectx.absolute_positions then TPrinting.Printer.s_pos cm.cm_pos
  261. else Lexer.get_error_pos error_printer cm.cm_pos
  262. in
  263. let lines =
  264. match (ExtString.String.nsplit str "\n") with
  265. | first :: rest -> (cm.cm_depth, first) :: List.map (fun msg -> (cm.cm_depth+1, msg)) rest
  266. | l -> [(cm.cm_depth, List.hd l)]
  267. in
  268. let rm_prefix str = if (ExtString.String.starts_with str "... ") then String.sub str 4 ((String.length str) - 4) else str in
  269. Some (String.concat "\n" (List.map (fun (depth, msg) -> (String.make (depth*2) ' ') ^ epos ^ " : " ^ (rm_prefix msg)) lines))
  270. end
  271. let get_max_line max_lines messages =
  272. List.fold_left (fun max_lines cm ->
  273. let _,_,l2,_ = Lexer.get_pos_coords cm.cm_pos in
  274. let old = try IntMap.find cm.cm_depth max_lines with Not_found -> 0 in
  275. if l2 > old then IntMap.add cm.cm_depth l2 max_lines
  276. else max_lines
  277. ) max_lines messages
  278. let display_source_at com p =
  279. let absolute_positions = Define.defined com.defines Define.MessageAbsolutePositions in
  280. let ectx = create_error_context absolute_positions in
  281. let msg = make_compiler_message "" p 0 MessageKind.DKCompilerMessage MessageSeverity.Information in
  282. ectx.max_lines <- get_max_line ectx.max_lines [msg];
  283. match compiler_pretty_message_string com ectx msg with
  284. | None -> ()
  285. | Some s -> prerr_endline s
  286. exception ConfigError of string
  287. let get_formatter com def default =
  288. let format_mode = Define.defined_value_safe ~default com.defines def in
  289. match format_mode with
  290. | "pretty" -> compiler_pretty_message_string com
  291. | "indent" -> compiler_indented_message_string
  292. | "classic" -> compiler_message_string
  293. | m -> begin
  294. let def = Define.get_define_key def in
  295. raise (ConfigError (Printf.sprintf "Invalid message reporting mode: \"%s\", expected classic | pretty | indent (for -D %s)." m def))
  296. end
  297. let print_error (err : Error.error) =
  298. let ret = ref "" in
  299. Error.recurse_error (fun depth err ->
  300. ret := !ret ^ (Lexer.get_error_pos (Printf.sprintf "%s:%d: ") err.err_pos) ^ (Error.error_msg err.err_message) ^ "\n"
  301. ) err;
  302. !ret
  303. let format_messages com messages =
  304. let absolute_positions = Define.defined com.defines Define.MessageAbsolutePositions in
  305. let ectx = create_error_context absolute_positions in
  306. ectx.max_lines <- get_max_line ectx.max_lines messages;
  307. let message_formatter = get_formatter com Define.MessageReporting "pretty" in
  308. let lines = List.rev (
  309. List.fold_left (fun lines cm -> match (message_formatter ectx cm) with
  310. | None -> lines
  311. | Some str -> str :: lines
  312. ) [] messages
  313. ) in
  314. ExtLib.String.join "\n" lines
  315. let display_messages ctx on_message = begin
  316. let absolute_positions = Define.defined ctx.com.defines Define.MessageAbsolutePositions in
  317. let ectx = create_error_context absolute_positions in
  318. ectx.max_lines <- get_max_line ectx.max_lines ctx.messages;
  319. let error msg =
  320. ctx.has_error <- true;
  321. on_message MessageSeverity.Error msg
  322. in
  323. let get_formatter _ def default =
  324. try get_formatter ctx.com def default
  325. with | ConfigError s ->
  326. error s;
  327. compiler_message_string
  328. in
  329. let message_formatter = get_formatter ctx.com Define.MessageReporting "pretty" in
  330. let log_formatter = get_formatter ctx.com Define.MessageLogFormat "indent" in
  331. let log_messages = ref (Define.defined ctx.com.defines Define.MessageLogFile) in
  332. let log_message = ref None in
  333. let close_logs = ref None in
  334. if !log_messages then begin
  335. try begin
  336. let buf = Rbuffer.create 16000 in
  337. let file = Define.defined_value ctx.com.defines Define.MessageLogFile in
  338. let chan =
  339. Path.mkdir_from_path file;
  340. open_out_bin file
  341. in
  342. log_message := (Some (fun msg ->
  343. match (log_formatter ectx msg) with
  344. | None -> ()
  345. | Some str -> Rbuffer.add_string buf (str ^ "\n")));
  346. close_logs := (Some (fun () ->
  347. Rbuffer.output_buffer chan buf;
  348. Rbuffer.clear buf;
  349. close_out chan
  350. ));
  351. end with
  352. | Failure e | Sys_error e -> begin
  353. let def = Define.get_define_key Define.MessageLogFile in
  354. error (Printf.sprintf "Error opening log file: %s. Logging to file disabled (-D %s)" e def);
  355. log_messages := false;
  356. end
  357. end;
  358. List.iter (fun cm ->
  359. if !log_messages then (Option.get !log_message) cm;
  360. match (message_formatter ectx cm) with
  361. | None -> ()
  362. | Some str -> on_message cm.cm_severity str
  363. ) (List.rev ctx.messages);
  364. if !log_messages then (Option.get !close_logs) ();
  365. end