open Extlib_leftovers open Globals open Common open CompilationContext let resolve_source file l1 p1 l2 p2 = if l1 = l2 && p1 = p2 && l1 = 1 && p1 = 1 then [] else begin let ch = open_in_bin file in let curline = ref 1 in let lines = ref [] in let rec loop p line = let inc i line = if (!curline >= l1) && (!curline <= l2) then lines := (!curline, line) :: !lines; incr curline; (i, "") in let input_char_or_done ch line = try input_char ch with End_of_file -> begin ignore(inc 0 line); raise End_of_file end in let read_char line = match input_char_or_done ch line with | '\n' -> inc 1 line | '\r' -> ignore(input_char_or_done ch line); inc 2 line | c -> begin let line = ref (line ^ (String.make 1 c)) in let rec skip n = if n > 0 then begin let c = input_char_or_done ch !line in line := !line ^ (String.make 1 c); skip (n - 1) end in let code = int_of_char c in if code < 0xC0 then () else if code < 0xE0 then skip 1 else if code < 0xF0 then skip 2 else skip 3; (1, !line) end in let (delta, line) = read_char line in loop (p + delta) line in try loop 0 ""; with End_of_file -> close_in ch; List.rev !lines end let error_printer file line = Printf.sprintf "%s:%d:" file line type error_context = { absolute_positions : bool; mutable last_positions : pos IntMap.t; mutable max_lines : int IntMap.t; mutable gutter : int IntMap.t; mutable previous : (pos * MessageSeverity.t * int) option; } let create_error_context absolute_positions = { absolute_positions = absolute_positions; last_positions = IntMap.empty; max_lines = IntMap.empty; gutter = IntMap.empty; previous = None; } let compiler_pretty_message_string com ectx cm = match cm.cm_message with (* Filter some messages that don't add much when using this message renderer *) | "End of overload failure reasons" -> None | _ -> begin ectx.last_positions <- (IntMap.add cm.cm_depth cm.cm_pos ectx.last_positions); let is_null_pos = cm.cm_pos = null_pos || cm.cm_pos.pmin = -1 in let is_unknown_file f = f = "" || f = "?" in (* Extract informations from position *) let l1, p1, l2, p2, epos, lines = if is_null_pos then begin let epos = if is_unknown_file cm.cm_pos.pfile then "(unknown position)" else cm.cm_pos.pfile in (-1, -1, -1, -1, epos, []) end else try begin let l1, p1, l2, p2 = Lexer.get_pos_coords cm.cm_pos in let lines = resolve_source cm.cm_pos.pfile l1 p1 l2 p2 in let epos = if lines = [] then cm.cm_pos.pfile else if ectx.absolute_positions then TPrinting.Printer.s_pos cm.cm_pos else Lexer.get_error_pos error_printer cm.cm_pos in (l1, p1, l2, p2, epos, lines) end with Not_found | Sys_error _ -> (1, 1, 1, 1, cm.cm_pos.pfile, []) in (* If 4 lines or less, display all; if more, crop the middle *) let lines = match lines with | _ :: (_ :: (_ :: (_ :: []))) -> lines | hd :: (_ :: (_ :: (_ :: l))) -> let _,line = hd in let indent = ref 0 in let found = ref false in while (not !found) && (!indent < (String.length line - 1)) do found := not (Lexer.is_whitespace (String.unsafe_get line !indent)); indent := !indent + 1 done; [hd; (0, (String.make (!indent+1) ' ') ^ "[...]"); List.hd (List.rev l)] | _ -> lines in let parent_pos = if cm.cm_depth = 0 then null_pos else (try IntMap.find (cm.cm_depth-1) ectx.last_positions with Not_found -> null_pos) in let prev_pos,prev_sev,prev_nl = match ectx.previous with | None -> (None, None, 0) | Some (p, sev, depth) -> (Some p, Some sev, depth) in let sev_changed = prev_sev = None || Some cm.cm_severity <> prev_sev in 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 let file_changed = prev_pos = None || (pos_changed && match (cm.cm_pos.pfile, (Option.get prev_pos).pfile) with | (f1, f2) when (is_unknown_file f1) && (is_unknown_file f2) -> false | (f1, f2) -> f1 <> f2 ) in let display_heading = cm.cm_depth = 0 || sev_changed || file_changed in let has_source = match lines with | [] -> false | _ -> true in let display_source = has_source && (cm.cm_depth = 0 || sev_changed || pos_changed) in let display_pos_marker = (not is_null_pos) && has_source && (cm.cm_depth = 0 || sev_changed || pos_changed) in let gutter_len = (try String.length (Printf.sprintf "%d" (IntMap.find cm.cm_depth ectx.max_lines)) with Not_found -> 0) + 2 in let no_color = Define.defined com.defines Define.MessageNoColor in let c_reset = if no_color then "" else "\x1b[0m" in let c_bold = if no_color then "" else "\x1b[1m" in let c_dim = if no_color then "" else "\x1b[2m" in let (c_sev, c_sev_bg) = if no_color then ("", "") else match cm.cm_severity with | MessageSeverity.Warning -> ("\x1b[33m", "\x1b[30;43m") | Information | Hint -> ("\x1b[34m", "\x1b[30;44m") | Error -> ("\x1b[31m", "\x1b[30;41m") in let sev_label = if cm.cm_depth > 0 then " -> " else Printf.sprintf (if no_color then "[%s]" else " %s ") (match cm.cm_severity with | MessageSeverity.Warning -> "WARNING" | Information -> "INFO" | Hint -> "HINT" | Error -> "ERROR" ) in let out = ref "" in if display_heading then out := Printf.sprintf "%s%s%s\n\n" (* Severity heading *) (c_sev_bg ^ sev_label ^ c_reset ^ " ") (* Macro context indicator *) (if cm.cm_from_macro then c_sev ^ "(macro) " ^ c_reset else "") (* File + line pointer *) epos; (* Macros can send all sorts of bad positions; avoid failing too hard *) let safe_sub s pos len = if len < 0 then "" else let pos = if pos < 0 then 0 else pos in let slen = String.length s in if pos >= slen then "" else let len = if (pos + len) > slen then slen - pos else len in try String.sub s pos len with (* Should not happen anymore, but still better than a crash if I missed some case... *) | Invalid_argument _ -> (Printf.sprintf "[%s;%i;%i]" s pos len) in (* Error source *) if display_source then out := List.fold_left (fun out (l, line) -> let nb_len = String.length (string_of_int l) in let gutter = gutter_len - nb_len - 1 in (* Replace tabs with 1 space to avoid column misalignments *) let line = String.concat " " (ExtString.String.nsplit line "\t") in let len = String.length line in out ^ Printf.sprintf "%s%s | %s\n" (* left-padded line number *) (if gutter < 1 then "" else String.make gutter ' ') (if l = 0 then "-" else Printf.sprintf "%d" l) (* Source code at that line *) ( if l = 0 then c_dim ^ line ^ c_reset else if l1 = l2 then (if p1 > 1 then c_dim ^ (safe_sub line 0 (p1-1)) else "") ^ c_reset ^ c_bold ^ (safe_sub line (p1-1) (p2-p1)) ^ c_reset ^ c_dim ^ (safe_sub line (p2-1) (len - p2 + 1)) ^ c_reset else begin (if (l = l1) then c_dim ^ (safe_sub line 0 (p1-1)) ^ c_reset ^ c_bold ^ (safe_sub line (p1-1) (len-p1+1)) ^ c_reset else if (l = l2) then c_bold ^ (safe_sub line 0 (p2-1)) ^ c_reset ^ c_dim ^ (safe_sub line (p2-1) (len-p2+1)) ^ c_reset else c_bold ^ line ^ c_reset) end ) ) !out lines; (* Error position marker *) if display_pos_marker then out := Printf.sprintf "%s%s|%s\n" !out (String.make gutter_len ' ') (if l1 = l2 then String.make p1 ' ' ^ c_sev ^ String.make (if p1 = p2 then 1 else p2-p1) '^' ^ c_reset else ""); (* Error message *) out := List.fold_left (fun out str -> Printf.sprintf "%s%s| %s\n" out (String.make gutter_len ' ') (* Remove "... " prefix *) (if (ExtString.String.starts_with str "... ") then String.sub str 4 ((String.length str) - 4) else str) ) !out (ExtString.String.nsplit cm.cm_message "\n"); ectx.previous <- Some ((if is_null_pos then null_pos else cm.cm_pos), cm.cm_severity, cm.cm_depth); ectx.gutter <- (IntMap.add cm.cm_depth gutter_len ectx.gutter); (* Indent sub errors *) let rec indent ?(acc=0) depth = if depth = 0 then acc else indent ~acc:(acc + try IntMap.find (depth-1) ectx.gutter with Not_found -> 3) (depth-1) in Some ( if cm.cm_depth > 0 then String.concat "\n" (List.map (fun str -> match str with | "" -> "" | _ -> (String.make (indent cm.cm_depth) ' ') ^ str ) (ExtString.String.nsplit !out "\n")) else !out ) end let compiler_message_string ectx cm = let str = match cm.cm_severity with | MessageSeverity.Warning -> "Warning : " ^ cm.cm_message | Information | Error | Hint -> cm.cm_message in if cm.cm_pos = null_pos then Some str else begin let epos = if ectx.absolute_positions then TPrinting.Printer.s_pos cm.cm_pos else Lexer.get_error_pos error_printer cm.cm_pos in let str = let lines = match (ExtString.String.nsplit str "\n") with | first :: rest -> first :: List.map Error.compl_msg rest | l -> l in String.concat ("\n" ^ epos ^ " : ") lines in Some (Printf.sprintf "%s : %s" epos str) end let compiler_indented_message_string ectx cm = match cm.cm_message with (* Filter some messages that don't add much when using this message renderer *) | "End of overload failure reasons" -> None | _ -> let str = match cm.cm_severity with | MessageSeverity.Warning -> "Warning : " ^ cm.cm_message | Information -> "Info : " ^ cm.cm_message | Error | Hint -> cm.cm_message in if cm.cm_pos = null_pos then Some str else begin let epos = if ectx.absolute_positions then TPrinting.Printer.s_pos cm.cm_pos else Lexer.get_error_pos error_printer cm.cm_pos in let lines = match (ExtString.String.nsplit str "\n") with | first :: rest -> (cm.cm_depth, first) :: List.map (fun msg -> (cm.cm_depth+1, msg)) rest | l -> [(cm.cm_depth, List.hd l)] in let rm_prefix str = if (ExtString.String.starts_with str "... ") then String.sub str 4 ((String.length str) - 4) else str in Some (String.concat "\n" (List.map (fun (depth, msg) -> (String.make (depth*2) ' ') ^ epos ^ " : " ^ (rm_prefix msg)) lines)) end let get_max_line max_lines messages = List.fold_left (fun max_lines cm -> let _,_,l2,_ = Lexer.get_pos_coords cm.cm_pos in let old = try IntMap.find cm.cm_depth max_lines with Not_found -> 0 in if l2 > old then IntMap.add cm.cm_depth l2 max_lines else max_lines ) max_lines messages let display_source_at com p = let absolute_positions = Define.defined com.defines Define.MessageAbsolutePositions in let ectx = create_error_context absolute_positions in let msg = make_compiler_message "" p 0 MessageKind.DKCompilerMessage MessageSeverity.Information in ectx.max_lines <- get_max_line ectx.max_lines [msg]; match compiler_pretty_message_string com ectx msg with | None -> () | Some s -> prerr_endline s exception ConfigError of string let get_formatter com def default = let format_mode = Define.defined_value_safe ~default com.defines def in match format_mode with | "pretty" -> compiler_pretty_message_string com | "indent" -> compiler_indented_message_string | "classic" -> compiler_message_string | m -> begin let def = Define.get_define_key def in raise (ConfigError (Printf.sprintf "Invalid message reporting mode: \"%s\", expected classic | pretty | indent (for -D %s)." m def)) end let print_error (err : Error.error) = let ret = ref "" in Error.recurse_error (fun depth err -> ret := !ret ^ (Lexer.get_error_pos (Printf.sprintf "%s:%d: ") err.err_pos) ^ (Error.error_msg err.err_message) ^ "\n" ) err; !ret let format_messages com messages = let absolute_positions = Define.defined com.defines Define.MessageAbsolutePositions in let ectx = create_error_context absolute_positions in ectx.max_lines <- get_max_line ectx.max_lines messages; let message_formatter = get_formatter com Define.MessageReporting "pretty" in let lines = List.rev ( List.fold_left (fun lines cm -> match (message_formatter ectx cm) with | None -> lines | Some str -> str :: lines ) [] messages ) in ExtLib.String.join "\n" lines let display_messages ctx on_message = begin let absolute_positions = Define.defined ctx.com.defines Define.MessageAbsolutePositions in let ectx = create_error_context absolute_positions in ectx.max_lines <- get_max_line ectx.max_lines ctx.messages; let error msg = ctx.has_error <- true; on_message MessageSeverity.Error msg in let get_formatter _ def default = try get_formatter ctx.com def default with | ConfigError s -> error s; compiler_message_string in let message_formatter = get_formatter ctx.com Define.MessageReporting "pretty" in let log_formatter = get_formatter ctx.com Define.MessageLogFormat "indent" in let log_messages = ref (Define.defined ctx.com.defines Define.MessageLogFile) in let log_message = ref None in let close_logs = ref None in if !log_messages then begin try begin let buf = Rbuffer.create 16000 in let file = Define.defined_value ctx.com.defines Define.MessageLogFile in let chan = Path.mkdir_from_path file; open_out_bin file in log_message := (Some (fun msg -> match (log_formatter ectx msg) with | None -> () | Some str -> Rbuffer.add_string buf (str ^ "\n"))); close_logs := (Some (fun () -> Rbuffer.output_buffer chan buf; Rbuffer.clear buf; close_out chan )); end with | Failure e | Sys_error e -> begin let def = Define.get_define_key Define.MessageLogFile in error (Printf.sprintf "Error opening log file: %s. Logging to file disabled (-D %s)" e def); log_messages := false; end end; List.iter (fun cm -> if !log_messages then (Option.get !log_message) cm; match (message_formatter ectx cm) with | None -> () | Some str -> on_message cm.cm_severity str ) (List.rev ctx.messages); if !log_messages then (Option.get !close_logs) (); end