File ‹Feedback.ML›
structure C_Files = Theory_Data
(struct
type T = {main: string,
files: ({sources: (Token.file * Position.T ) Symtab.table, cpp_output:string list} Symtab.table)}
val empty = {main = "", files = Symtab.empty}
val merge = Utils.fast_merge (fn ({files=files1,...}, {files=files2,...}) =>
{main = "", files = Symtab.join (fn _ => fn (
{sources = sources1, cpp_output = cpp_output1}, {sources = sources2, cpp_output = cpp_output2})=>
{sources = Symtab.merge (K false) (sources1, sources2),
cpp_output = if pointer_eq (cpp_output1, cpp_output2) then cpp_output1 else error "cpp_output should be identical"})
(files1, files2)})
end);
structure C_Files =
struct
open C_Files
val get_main = #main o get
val get_files = #files o get
fun map_main' f {main, files} = {main = f main, files = files}
fun map_files' f {main, files} = {main = main, files = f files}
fun map_sources' f {sources, cpp_output} = {sources = f sources, cpp_output = cpp_output}
fun map_cpp_output' f {sources, cpp_output} = {sources = sources, cpp_output = f cpp_output}
val map_main = map o map_main'
val map_files = map o map_files'
fun get_current_files thy =
let
val {main, files} = get thy
in the_default Symtab.empty (Option.map #sources (Symtab.lookup files main)) end
val default_file = ({src_path = Path.root, lines = [], digest=SHA1.fake "", pos = Position.none}, Position.none)
fun get_file thy main name = Symtab.lookup (#files (get thy)) main
|> Option.mapPartial (fn {sources, ...} => Symtab.lookup sources name)
fun get_cpp_output thy main = Symtab.lookup (#files (get thy)) main
|> Option.map (#cpp_output)
fun map_file main name f =
map (map_files'
(Symtab.map_default (main, {sources = Symtab.empty, cpp_output = []})
(map_sources' (Symtab.map_default (name, default_file) f))))
fun map_cpp_output main f =
map (map_files'
(Symtab.map_default (main, {sources = Symtab.empty, cpp_output = []})
(map_cpp_output' f)))
fun get_current_file thy = get_file thy (get_main thy)
fun map_current_file name f thy = map_file (get_main thy) name f thy
fun get_current_cpp_output thy = get_cpp_output thy (get_main thy)
fun map_current_cpp_output f thy = map_cpp_output (get_main thy) f thy
end
signature FEEDBACK =
sig
exception WantToExit of string
val level : int Config.T
val verbose : bool Config.T
val feedback_as_error: bool Config.T
val expect_reject : bool Config.T
val get_num_errors_val : Proof.context -> int
val reset_num_errors : Proof.context -> Proof.context
val get_threshold : Proof.context -> int option
val map_threshold : (int option -> int option) -> Proof.context -> Proof.context
val transfer_error_state : Proof.context -> Proof.context -> Proof.context
val errorStr : Proof.context -> Region.t * string -> unit
val errorStr' : Proof.context -> SourcePos.t * SourcePos.t * string -> unit
val warnStr' : Proof.context -> SourcePos.t * SourcePos.t * string -> unit
val informStr : Proof.context -> int * string -> unit
val informStr' : Proof.context -> int * SourcePos.t * SourcePos.t * string -> unit
val writelns: Proof.context -> int * (string list) -> unit
val error_region : Proof.context -> Region.t -> string -> 'a
val error_range : Proof.context -> SourcePos.t -> SourcePos.t -> string -> 'a
val error_pos : Proof.context -> SourcePos.t -> string -> 'a
val report_error : (string * Position.T option) -> unit
val report_warning : (string * Position.T option) -> unit
val report_inform : (string * Position.T option) -> unit
val errorf : ((string * Position.T option) -> unit) Unsynchronized.ref
val warnf : ((string * Position.T option) -> unit) Unsynchronized.ref
val informf : ((string * Position.T option) -> unit) Unsynchronized.ref
val timestamp : string -> string
val in_target : xstring -> (local_theory -> local_theory) -> local_theory -> local_theory
end
structure Feedback :> FEEDBACK =
struct
val expect_reject = Attrib.setup_config_bool @{binding "c_parser_expect_reject"} (K false)
val level = Attrib.setup_config_int @{binding "c_parser_feedback_level"} (K 0)
val verbose = Attrib.setup_config_bool @{binding "c_parser_verbose"} (K false)
val feedback_as_error = Attrib.setup_config_bool @{binding "c_parser_feedback_as_error"} (K false)
datatype truncate = truncate_at_line_begin | truncate_at_line_end | no_truncate
fun pos_of_cpos trunc (file:Token.file) cpos = if SourcePos.is_bogus cpos then Position.none else
let
val c = SourcePos.column cpos
val l = SourcePos.line cpos
val {lines, pos,...} = file
val (lines_before, lines_end) = chop (l - 1) lines
val offset_lines = fold (fn l => fn off => off + (length (Symbol.explode l)) + 1) lines_before 1
val line = hd lines_end
val max_column = length (Symbol.explode line)
val c' = if c <= max_column then c else
case trunc of
truncate_at_line_begin => 0
| truncate_at_line_end => max_column
| no_truncate => c
val offset = offset_lines + c'
val end_offset = 0
in
Position.make {line = l, offset = offset, end_offset= end_offset, props = #props (Position.dest pos)}
end
fun map_column f p = SourcePos.make {column = f (SourcePos.column p), file = SourcePos.file p, line = SourcePos.line p, yypos = SourcePos.yypos p}
fun pos_of_crange file cpos1 cpos2 =
let
val pos1 = pos_of_cpos truncate_at_line_begin file cpos1
val pos2 = pos_of_cpos truncate_at_line_end file (map_column (fn n => n + 1) cpos2)
in
Position.range_position (pos1, pos2)
end
fun pos_of_cregion (file: Token.file) region =
let
val fpos = #pos file
in
case Region.left region of
NONE => fpos
| SOME l => (case Region.right region of
NONE => pos_of_cpos truncate_at_line_end file l
| SOME r => pos_of_crange file l r)
end
val empty_pos = {line = ~1, col = ~1}
fun is_empty_pos pos = (pos = empty_pos)
fun inter_region (begin1, end1) (begin2, end2) = (Int.max (begin1, begin2), Int.min (end1, end2))
fun is_empty_region (begin, the_end) = (the_end <= begin)
fun get_cpp_output_span ctxt region =
Region.left region |> Option.mapPartial (fn l =>
Region.right region |> Option.mapPartial (fn r =>
C_Files.get_current_cpp_output (Proof_Context.theory_of ctxt) |> Option.map (fn cpp_output =>
let
val left = SourcePos.yypos l - 1
val right = SourcePos.yypos r - 1
fun has_overlap p = not (is_empty_region (inter_region (left, right) p))
fun chop_lines (offset, current_line, begin_pos, end_pos) result_lines input_lines =
if offset >= right then (result_lines, begin_pos, end_pos)
else
(case input_lines of [] => (result_lines, begin_pos, end_pos)
| (l::ls) =>
let
val l' = Symbol.explode l
val len = length l'
val line_end_offset = offset + len + 1
val line_overlap = has_overlap (offset, line_end_offset)
val left' = left - offset
val begin_line_match =
if line_overlap then
if left' >= 0 then (drop left' l') else l'
else []
val (begin_pos', col) =
if is_empty_pos begin_pos andalso line_overlap then ({line = current_line, col = left'}, left')
else (begin_pos, 0)
val remaining_span = right + 1 - (offset + col)
val inside_region = take remaining_span (begin_line_match)
val end_pos' = if not (null inside_region) andalso line_end_offset >= right then {line = current_line, col = right - offset} else end_pos
val result_lines' = if null inside_region then result_lines else result_lines @ [implode inside_region]
in chop_lines (line_end_offset, current_line + 1, begin_pos', end_pos') result_lines' ls end)
in
chop_lines (0, 0, empty_pos, empty_pos ) [] cpp_output
end ))) |> the_default ([], empty_pos, empty_pos)
exception WantToExit of string
structure Data = Proof_Data(
struct
type T = int Synchronized.var * (int option)
val init = (fn _ => (Synchronized.var "num_errors" 0, NONE))
end
);
val get_num_errors = fst o Data.get
fun map_num_errors f = Data.map (apfst f)
fun transfer_error_state ctxt1 ctxt2 = Data.map (K (Data.get ctxt1)) ctxt2
val get_num_errors_val = Synchronized.value o get_num_errors
fun reset_num_errors ctxt = map_num_errors (K (Synchronized.var "num_errors" 0)) ctxt
fun incr_num_errors ctxt =
let
val v = get_num_errors ctxt
in Synchronized.guarded_access v (fn n => SOME (n, n + 1)) end
val get_threshold = snd o Data.get
fun map_threshold f = Data.map (apsnd f)
fun command_output output (s, NONE) = output s
| command_output output (s, SOME command_thread_pos) =
let
val current_thread_pos = Position.thread_data ()
val _ =
if command_thread_pos <> current_thread_pos then
Position.setmp_thread_data command_thread_pos output s
else ()
in
output s
end
val report_error = command_output Output.error_message
val report_warning = command_output warning
val report_inform = command_output tracing
val errorf = Unsynchronized.ref report_error
val warnf = Unsynchronized.ref report_warning
val informf = Unsynchronized.ref report_inform
val _ = Option.map
fun message ctxt r s =
let
val thy = Proof_Context.theory_of ctxt
val file = \<^try>‹Region.left r
|> Option.map (SourcePos.file #> Path.explode #> Path.base #> Path.file_name)
|> Option.mapPartial (C_Files.get_current_file thy)
catch _ => NONE›
in
case file of
NONE => (Region.toString r ^ ": " ^ s, NONE)
| SOME (f, command_thread_pos) =>
let
val pos = pos_of_cregion f r
val (relevant_cpp_source, {line=begin_cpp_line, col=begin_cpp_col}, {line=end_cpp_line, col=end_cpp_col}) = get_cpp_output_span ctxt r
val src = if null relevant_cpp_source then "" else
":\n cpp output in error region (" ^
string_of_int (begin_cpp_line + 1) ^ ":" ^ string_of_int (begin_cpp_col + 1) ^ " - " ^
string_of_int (end_cpp_line + 1) ^ ":" ^ string_of_int (end_cpp_col + 1) ^ "):\n " ^ cat_lines relevant_cpp_source
val str = s ^ ": " ^ Position.here pos ^ src
in
(str, SOME command_thread_pos)
end
end
fun message' ctxt l r = message ctxt (Region.make {left = l, right = r})
fun message_pos ctxt p = message ctxt (Region.make {left = p, right = SourcePos.bogus})
fun writelns ctxt (v, s) = if v <= Config.get ctxt level then Output.writelns s else ()
fun informStr0 ctxt (v,s) = if v <= Config.get ctxt level then !informf s else ()
fun informStr' ctxt (v,l,r,s) =
informStr0 ctxt (v, message' ctxt l r s)
fun informStr ctxt (v, s) = informStr0 ctxt (v, (s, NONE))
fun errf ctxt = if Config.get ctxt expect_reject then !warnf else !errorf
fun errorStr ctxt (r, s) =
let
val msg = message ctxt r s
in
if Config.get ctxt feedback_as_error then
error (fst msg)
else
let
val _ = errf ctxt msg
val n = incr_num_errors ctxt
val threshold = get_threshold ctxt
in
if is_some threshold andalso n > the threshold then
raise WantToExit "Too many errors - aborted."
else ()
end
end
fun errorStr' ctxt (l,r,s) = errorStr ctxt (Region.make {left = l, right = r}, s)
fun warnStr' ctxt (l,r,s) =
!warnf ( message' ctxt l r s |> apfst (prefix "Warning "))
fun timestamp s = Time.fmt 0 (Time.now()) ^ ": " ^ s
fun raise_error ctxt (msg as (s, _)) =
(errf ctxt msg; error s)
fun error_region ctxt r s = raise_error ctxt (message ctxt r s)
fun error_range ctxt l r s = raise_error ctxt (message' ctxt l r s)
fun error_pos ctxt p s = raise_error ctxt (message_pos ctxt p s)
fun in_target name f lthy =
let
val (reenter, target_lthy) = Target_Context.switch_named_cmd (SOME (name, Position.none)) (Context.Proof lthy)
in
target_lthy
|> transfer_error_state lthy
|> f
|> (fn lthy' =>
lthy'
|> reenter
|> Context.the_proof
|> transfer_error_state lthy')
end
end;
structure More_Local_Theory =
struct
open More_Local_Theory
fun in_theory f = gen_in_theory Feedback.transfer_error_state f
fun in_theory_result f = gen_in_theory_result Feedback.transfer_error_state f
end