diff --git a/doc/mercury_user_guide.texi b/doc/mercury_user_guide.texi index fa6d4a6cf..b511a6358 100644 --- a/doc/mercury_user_guide.texi +++ b/doc/mercury_user_guide.texi @@ -6780,6 +6780,10 @@ i.e.@: sets the counts for all depths to zero. @sp 1 @item dice [-p@var{filename}] [-f@var{filename}] [-n@var{num}] [-s[pPfFsS]+] [-o @var{filename}] [-m @var{module}] @kindex dice (mdb command) +@c NOTE Much of the description of this command +@c is included in the user guide TWICE, +@c once here, and once in the description of the mdice command below. +@c Changes here may require changes there, and vice versa. Display a program dice on the screen. @sp 1 A dice is a comparison between @@ -6826,69 +6830,91 @@ Six columns are displayed: @item @samp{Procedure}: The procedure in which the goal appears. @item @samp{Path/Port}: -The goal path and/or port of the goal. For atomic goals, statistics about the -CALL event and the corresponding EXIT, FAIL or EXCP event are displayed on -separate rows. For other types of goals the goal path is displayed, except for -NEGE, NEGS and NEGF events where the goal path and port are displayed. +The goal path and/or port of the goal. +For atomic goals, statistics about the CALL event +and the corresponding EXIT, FAIL or EXCP event +are displayed on separate rows. +For other types of goals, the displayed text shows the goal path, +except for NEGE, NEGS and NEGF events, +where it contains both the goal path and the port. @item @samp{File:Line}: -The file name and line number of the goal. This can be used to set a -breakpoint on the goal. +The file name and line number of the goal. +This can be used to set a breakpoint on the goal. @item @samp{Pass (total passing test runs)}: The total number of times the goal was executed in all the passing test runs. -This is followed by a number in parentheses which indicates the number of test -runs the goal was executed in. The heading of this column also has a number in -parentheses which is the total number of passing test cases. In the example -above we can see that 3 passing tests were run. +This is followed by a number in parentheses, +which indicates the number of test runs the goal was executed in. +The heading of this column also has a number in parentheses, +which is the total number of passing test cases. +In the example above we can see that three passing tests were run. @item @samp{Fail}: The number of times the goal was executed in the failing test run. @item @samp{Suspicion}: -A number between 0 and 1 which gives an indication of how likely a -particular goal is to be buggy. -This is calculated as Suspicion = F / (P + F) where F is the number of times -the goal was executed in the failing test run and P is the number of times the -goal was executed in passing test runs. +A number between 0 and 1 which gives an indication +of how likely a particular goal is to be buggy. +This is calculated as Suspicion = Fail / (Pass + Fail), where +Fail is the number of times the goal was executed in the failing test run, +and Pass is the number of times the goal was executed in passing test runs. @end itemize @sp 1 -The name of the file containing the failing slice can be specified with the -@samp{-f} or @samp{--fail-trace-counts} option or with a separate -@samp{set fail_trace_counts @var{filename}} command. +The name of the file containing the failing slice +can be specified with the @samp{-f} or @samp{--fail-trace-counts} option +or with a separate @samp{set fail_trace_counts @var{filename}} command. @sp 1 The name of the file containing the union of the passing slices can be given with the @samp{-p} or @samp{--pass-trace-counts} option. -Alternatively a separate @samp{set pass_trace_counts @var{filename}} command -can be given. See @ref{Trace counts} for more information about trace counts. +Alternatively, users can give +a separate @samp{set pass_trace_counts @var{filename}} command +See @ref{Trace counts} for more information about trace counts. @sp 1 -The table can be sorted on the Pass, Fail or Suspicion columns, or a -combination of these. This can be done with the @samp{-s} or @samp{--sort} -option. The argument of this option is a string made up of any combination of -the letters @samp{pPfFsS}. The letters in the string indicate how the table -should be sorted: +The table is normally sorted on the identification of the event, +meaning it is sorted +@itemize @bullet +@item first on the identification of the procedure containing the event +(consisting of a pred/func indication, +the name of the predicate or function, its arity, and its mode number), +@item then on the port (if any), +@item then on the path (if any), +@item then on the file name, and +@item then on the line number. +@end itemize +@sp 1 +However, users can also ask for it to be sorted +on the Pass, Fail and/or Suspicion columns, or on a combination of these. +This can be done with the @samp{-s} or @samp{--sort} option. +The argument of this option is a nonempty string +consisting of any combination of the letters @samp{pPfFdDsS}. +The letters in the string indicate how the table should be sorted: @sp 1 @itemize @bullet @item @samp{p}: Pass ascending @item @samp{P}: Pass descending @item @samp{f}: Fail ascending @item @samp{F}: Fail descending +@item @samp{d}: Difference (Pass minus Fail) ascending +@item @samp{D}: Difference (Pass minus Fail) descending @item @samp{s}: Suspicion ascending @item @samp{S}: Suspicion descending @end itemize @sp 1 -For example, the string "SF" means sort the table by suspicion, descending, and -if any two suspicions are the same, then by number of executions in the failing -test case, descending. -@sp 1 -The option @samp{-n} or @samp{--top} can be used to limit the number lines -displayed. Only the top @var{num} lines, with respect to -the ordering specified by the @samp{-s} option, will be displayed. -By default the table is limited to 50 lines. -@sp 1 -If the @samp{-o} or @samp{--output-to-file} option is given then the output -will be written to the specified file instead of being displayed on the -screen. Note that the file will be overwritten without warning if it -already exists. -@sp 1 -The @samp{-m} or @samp{--module} option limits the output to the given module -and its submodules, if any. +For example, the string "SF" means sort the table by suspicion, descending, +and if any two suspicions are the same, +then by number of executions in the failing test case, descending. +@sp 1 +To limit the number sorted lines displayed, +users can specify the @samp{-n} option or its @samp{--top} synonym, +which take an integer argument; +this will cause only specified number +of the top lines of the table to be displayed. +@sp 1 +The @samp{-m} or @samp{--module} option, if specified, +limits the output to the given module and (if they exist) its submodules. +@sp 1 +If the user specifies the @samp{-o} or @samp{--output-to-file} option, +then the output will be written to the specified file +instead of being displayed on the screen. +Note that if the file already exists, +it will be overwritten without warning. @end table @sp 1 @@ -7826,7 +7852,7 @@ For example: mtc ./myprog arg1 arg2 @end example @sp 1 -The program will run as usual, except that when it terminates +The program will run as usual, except that when it terminates, it will write the number of times each debugger event was executed to a trace count file. @sp 1 @@ -7906,9 +7932,9 @@ the count of the test cases that contributed to its contents in any useful way. @node Slicing @subsection Slicing -Once a slice has been generated -it can be viewed in various ways using the mslice tool. -The output of the mslice tool will look something like the following: +Once a slice has been generated, +it can be viewed in various ways using the @samp{mslice} tool. +The output of this tool will look something like the following: @sp 1 @example Procedure Path/Port File:Line Count (1) @@ -7919,7 +7945,7 @@ pred mrg.msort_n/4-0 EXIT mrg.m:33 12 (1) pred mrg.msort_n/4-0 mrg.m:35 12 (1) @end example @sp 1 -Each row corresponds to a label in the program. +Each row corresponds to a debugger event in the program. The meanings of the columns are as follows: @itemize @bullet @item @samp{Procedure}: @@ -7937,12 +7963,12 @@ The number in parentheses for each event row says in how many runs the event was executed. The number in parentheses in the heading row (after the word "Count") indicates how many runs were represented -in the trace counts file analysed by the mslice tool. +in the trace counts file analysed by the @samp{mslice} tool. @end itemize @sp 1 -The mslice tool is invoked using a command of the form: +The @samp{mslice} tool is invoked using a command of the form: @example mslice [-s sortspec] [-l N] [-m module] [-n N] [-p N] [-f N] file @end example @@ -7970,17 +7996,18 @@ then those events will be sorted by number of runs in descending order. @sp 1 The default is to sort descending on the Count column. @sp 1 -The @samp{-l} or @samp{--limit} option limits the output to @samp{N} lines. +The @samp{-l} or @samp{--limit} option +limits the output to the first @samp{N} lines. @sp 1 -The @samp{-m} or @samp{--module} option limits the output -to events only from the given module. +The @samp{-m} or @samp{--module} option +limits the output to events only from the given module. @sp 1 -The @samp{-n} or @samp{--max-name-column-width} option's argument gives the -maximum width of the column containing predicate and function names. +The @samp{-n} or @samp{--max-name-column-width} option's argument gives +the maximum width of the column containing predicate and function names. If the argument is zero, there is no maximum width. @sp 1 -The @samp{-p} or @samp{--max-path-column-width} option's argument gives the -maximum width of the column containing ports and goal paths. +The @samp{-p} or @samp{--max-path-column-width} option's argument gives +the maximum width of the column containing ports and goal paths. If the argument is zero, there is no maximum width. @sp 1 The @samp{-f} or @samp{--max-file-column-width} option's argument gives the @@ -7991,6 +8018,10 @@ If the argument is zero, there is no maximum width. @node Dicing @subsection Dicing +@c NOTE Much of the description of this command +@c is included in the user guide TWICE, +@c once in the description of the dice command in mdb above, and here. +@c Changes here may require changes there, and vice versa. A dice is a comparison between passing and failing runs of a program. @sp 1 Dice are created using the @samp{mdice} tool. @@ -7999,8 +8030,10 @@ one must first generate a set of trace count files for passing runs and a set of trace count files for failing runs using the @samp{mtc} tool (@ref{Generating trace counts}). Once this has been done, -and the union of each set computed using @samp{mtc_union}, -@samp{mdice} can be used to display a table of statistics +and the union of each set +(the set of passing runs and the set of failing runs) +has been computed using @samp{mtc_union}, +invoking g@samp{mdice} will display a table of statistics that compares the passing runs to the failing runs. @sp 1 Here is an example of the output of the @samp{mdice} tool: @@ -8013,7 +8046,8 @@ pred s.mrg/3-0 CALL s.m:64 18 (3) 7 0.28 pred s.mrg/3-0 EXIT s.m:64 18 (3) 7 0.28 @end example @sp 1 -This example tells us that the @samp{else} in @samp{s.m} on line 74 +This example tells us that +the @samp{else} event in @samp{s.m} on line 74 was executed once in the failing test run, but never during the passing test runs, so this would be a good place to start looking for a bug. @@ -8031,10 +8065,10 @@ for an explanation of interface and internal events.) @item @samp{File:Line}: This column displays the context of the event. @item @samp{Pass (total passing test runs)}: -This columns displays the total number of times +This column displays the total number of times the event was executed in all the passing test runs. This is followed by a number in parentheses -which indicates the number of test runs the event was executed in. +which indicates the number of passing test runs the event was executed in. The heading of this column also has a number in parentheses which is the total number of passing test cases. @item @samp{Fail}: @@ -8043,9 +8077,9 @@ the goal was executed in the failing test run(s). @item @samp{Suspicion}: This columns displays a number between 0 and 1 which gives an indication of how likely a particular goal is to contain a bug. -The suspicion is calculated as Suspicion = F / (P + F) -where F is the number of times the goal was executed in failing runs -and P is the number of times the goal was executed in passing runs. +This number is calculated as Suspicion = Fail / (Pass + Fail) +where Fail is the number of times the goal was executed in failing runs +and Pass is the number of times the goal was executed in passing runs. @end itemize @sp 1 The @samp{mdice} tool is invoked with a command of the form: @@ -8053,18 +8087,36 @@ The @samp{mdice} tool is invoked with a command of the form: @example mdice [-s sortspec] [-l N] [-m module] [-n N] [-p N] [-f N] passfile failfile @end example +@sp 1 +where +@itemize @bullet +@item @samp{passfile} is a trace count file, -generated either directly by a passing program run -or as the union of the trace count files of passing program runs. +generated either directly by a passing program run, +or as the union of the trace count files of passing program runs, and +@item @samp{failfile} is a trace count file, -generated either directly by a failing program run +generated either directly by a failing program run, or as the union of the trace count files of failing program runs. +@end itemize +@sp 1 +The table is normally sorted on the identification of the event, +meaning it is sorted +@itemize @bullet +@item first on the identification of the procedure containing the event +(consisting of a pred/func indication, +the name of the predicate or function, its arity, and its mode number), +@item then on the port (if any), +@item then on the path (if any), +@item then on the file name, and +@item then on the line number. +@end itemize @sp 1 -The table can be sorted on the Pass, Fail or Suspicion columns, -or a combination of these. +However, users can also ask for it to be sorted +on the Pass, Fail and/or Suspicion columns, or on a combination of these. This can be done with the @samp{-s} or @samp{--sort} option. -The argument of this option is a string -made up of any combination of the letters @samp{pPfFsS}. +The argument of this option is a nonempty string +consisting of any combination of the letters @samp{pPfFdDsS}. The letters in the string indicate how the table should be sorted: @sp 1 @itemize @bullet @@ -8072,6 +8124,8 @@ The letters in the string indicate how the table should be sorted: @item @samp{P}: Pass descending @item @samp{f}: Fail ascending @item @samp{F}: Fail descending +@item @samp{d}: Difference (Pass minus Fail) ascending +@item @samp{D}: Difference (Pass minus Fail) descending @item @samp{s}: Suspicion ascending @item @samp{S}: Suspicion descending @end itemize @@ -8081,13 +8135,18 @@ sort the table by suspicion in descending order, and if any two suspicions are the same, then by number of executions in the failing run(s), also in descending order. @sp 1 -The default is to sort descending on the Suspicion column. +The default is "S", meaning to only sort descending by suspicion. +(Note that this is different from +the default for the @samp{dice} command in @samp{mdb}.) @sp 1 -The option @samp{-l} or @samp{--limit} -can be used to limit the number of lines displayed. +To limit the number sorted lines displayed, +users can specify the @samp{-n} option or its @samp{--top} synonym, +which take an integer argument; +this will cause only specified number +of the top lines of the table to be displayed. @sp 1 -The @samp{-m} or @samp{--module} option -limits the output to the given module and any submodules. +The @samp{-m} or @samp{--module} option, if specified, +limits the output to the given module and (if they exist) its submodules. @sp 1 The @samp{-n} or @samp{--max-name-column-width} option's argument gives the maximum width of the column containing predicate and function names. diff --git a/mdbcomp/slice_and_dice.m b/mdbcomp/slice_and_dice.m index c21160ba8..dc050d6ad 100644 --- a/mdbcomp/slice_and_dice.m +++ b/mdbcomp/slice_and_dice.m @@ -56,7 +56,11 @@ % :- pred read_slice(string::in, maybe_error(slice)::out, io::di, io::uo) is det. - % read_slice_to_string(File, SortStr, MaxRows, MaybeMaxPredColumns, +:- type maybe_filter_zero + ---> do_not_filter_zero + ; do_filter_zero. + + % read_slice_to_string(File, Zero, SortStr, MaxRows, MaybeMaxPredColumns, % MaybeMaxPredColumns, MaybeMaxPathColumns, MaybeMaxFileColumns, % Module, SliceStr, Problem, !IO): % @@ -79,8 +83,8 @@ % contain a string describing the problem encountered and SliceStr % will be the empty string, otherwise Problem will be the empty string. % -:- pred read_slice_to_string(string::in, string::in, int::in, - maybe(int)::in, maybe(int)::in, maybe(int)::in, +:- pred read_slice_to_string(string::in, maybe_filter_zero::in, string::in, + int::in, maybe(int)::in, maybe(int)::in, maybe(int)::in, string::in, string::out, string::out, io::di, io::uo) is det. %---------------------------------------------------------------------------% @@ -156,25 +160,29 @@ maybe(int)::in, maybe(int)::in, maybe(int)::in, string::in, string::out, string::out, io::di, io::uo) is det. - % suspicion_ratio(PassCount, FailCount) = Suspicion. - % suspicion_ratio gives an indication of how likely a label is to - % be buggy based on how many times it was executed in passing and - % failing test runs. + % suspicion_ratio(PassCount, FailCount) = Suspicion: + % + % This function gives an indication of how likely a label is to + % be buggy, by considering how many times it was executed in passing + % and in failing test runs. % :- func suspicion_ratio(int, int) = float. % suspicion_ratio_normalised(PassCount, PassTests, FailCount, FailTests) - % = Suspicion. - % suspicion_ratio_normalised gives an indication of how likely a label is - % to be buggy based on how many times it was executed in passing and - % failing test runs and on how many passing and failing test runs there - % were. + % = Suspicion: + % + % This function return an indication of how likely a label is + % to be buggy. It does this by considering how many times it was executed + % in passing and in failing test runs, and how many passing and failing + % test runs there were. % :- func suspicion_ratio_normalised(int, int, int, int) = float. - % suspicion_ratio_binary(PassCount, FailCount) = Suspicion. - % suspicion_ration_binary returns 1 if PassCount is 0 and FailCount is - % > 0 and 0 otherwise. + % suspicion_ratio_binary(PassCount, FailCount) = Suspicion: + % + % This function returns + % - 1 if PassCount is 0 and FailCount is > 0, and + % - 0 otherwise. % :- func suspicion_ratio_binary(int, int) = float. @@ -187,13 +195,13 @@ :- import_module mdbcomp.rtti_access. :- import_module mdbcomp.sym_name. +:- import_module char. :- import_module cord. :- import_module float. :- import_module int. :- import_module list. :- import_module pair. :- import_module require. -:- import_module set. :- import_module string. %---------------------------------------------------------------------------% @@ -221,10 +229,10 @@ read_slice(File, Result, !IO) :- % to the mechanism for sorting and formatting dices below. % -read_slice_to_string(File, SortStr0, MaxRows, +read_slice_to_string(File, FilterZero, SortStr, MaxRows, MaybeMaxPredColumns, MaybeMaxPathColumns, MaybeMaxFileColumns, Module, SliceStr, Problem, !IO) :- - ( if slice_sort_string_is_valid(SortStr0) then + ( if slice_sort_string_is_valid(SortStr, SliceCmp) then read_slice(File, ReadSliceResult, !IO), ( ReadSliceResult = ok(Slice), @@ -236,16 +244,16 @@ read_slice_to_string(File, SortStr0, MaxRows, list.filter(slice_label_count_is_for_module(Module), LabelCounts, ModuleFilteredLabelCounts) ), - ( if string.append("z", SortStrPrime, SortStr0) then - SortStr = SortStrPrime, - list.filter(slice_label_count_is_zero, - ModuleFilteredLabelCounts, FilteredLabelCounts) - else - SortStr = SortStr0, + ( + FilterZero = do_not_filter_zero, FilteredLabelCounts = ModuleFilteredLabelCounts + ; + FilterZero = do_filter_zero, + list.filter(slice_label_count_is_nonzero, + ModuleFilteredLabelCounts, FilteredLabelCounts) ), - list.sort(slice_label_count_compare(SortStr), FilteredLabelCounts, - SortedLabelCounts), + list.sort(slice_label_count_compare(SliceCmp), + FilteredLabelCounts, SortedLabelCounts), ( if list.take(MaxRows, SortedLabelCounts, Taken) then TopNLabelCounts = Taken else @@ -453,19 +461,20 @@ dice_add_trace_count(fail_slice, LineNoAndCount, ExecCounts0, ExecCounts) :- slice_label_count_is_for_module(Module, slice_label_count(Label, _, _)) :- proc_label_is_for_module(Module, Label). -:- pred slice_label_count_is_zero(slice_label_count::in) is semidet. +:- pred slice_label_count_is_nonzero(slice_label_count::in) is semidet. -slice_label_count_is_zero(SliceLabelCount) :- +slice_label_count_is_nonzero(SliceLabelCount) :- SliceLabelCount ^ slc_counts ^ slice_count > 0. -:- pred slice_label_count_compare(string::in, +:- pred slice_label_count_compare(slice_cmp::in, slice_label_count::in, slice_label_count::in, builtin.comparison_result::out) is det. -slice_label_count_compare(SortStr, LabelCountA, LabelCountB, Result) :- - ( if SortStr = "" then - LabelCountA = slice_label_count(ProcLabelA, PathPortA, CountsA), - LabelCountB = slice_label_count(ProcLabelB, PathPortB, CountsB), +slice_label_count_compare(SliceCmp, LabelCountA, LabelCountB, Result) :- + LabelCountA = slice_label_count(ProcLabelA, PathPortA, ExecCountsA), + LabelCountB = slice_label_count(ProcLabelB, PathPortB, ExecCountsB), + ( + SliceCmp = compare_slice_locns, builtin.compare(ProcLabelResult, ProcLabelA, ProcLabelB), ( ProcLabelResult = (<), @@ -478,7 +487,7 @@ slice_label_count_compare(SortStr, LabelCountA, LabelCountB, Result) :- Result = (<) ; PathPortResult = (=), - builtin.compare(Result, CountsA, CountsB) + builtin.compare(Result, ExecCountsA, ExecCountsB) ; PathPortResult = (>), Result = (>) @@ -487,9 +496,10 @@ slice_label_count_compare(SortStr, LabelCountA, LabelCountB, Result) :- ProcLabelResult = (>), Result = (>) ) - else - slice_exec_count_compare(SortStr, - LabelCountA ^ slc_counts, LabelCountB ^ slc_counts, Result) + ; + SliceCmp = compare_slice_counts(HeadCmp, TailCmps), + slice_exec_count_compare(HeadCmp, TailCmps, ExecCountsA, ExecCountsB, + Result) ). :- pred compare_path_ports(path_port::in, path_port::in, @@ -539,59 +549,80 @@ compare_path_ports(PathPortA, PathPortB, Result) :- builtin.compare(Result, PathPortA, PathPortB) ). -:- pred slice_exec_count_compare(string::in, - slice_exec_count::in, slice_exec_count::in, +:- pred slice_exec_count_compare(slice_count_cmp::in, + list(slice_count_cmp)::in, slice_exec_count::in, slice_exec_count::in, builtin.comparison_result::out) is det. -slice_exec_count_compare(SortStr, ExecCount1, ExecCount2, Result) :- - ( if string.first_char(SortStr, C, SortStrTail) then - ( if - ( - C = 'c', - CntA = ExecCount1 ^ slice_count, - CntB = ExecCount2 ^ slice_count - ; - C = 'C', - CntA = ExecCount2 ^ slice_count, - CntB = ExecCount1 ^ slice_count - ; - C = 't', - CntA = ExecCount1 ^ slice_tests, - CntB = ExecCount2 ^ slice_tests - ; - C = 'T', - CntA = ExecCount2 ^ slice_tests, - CntB = ExecCount1 ^ slice_tests - ) - then - builtin.compare(Result0, CntA, CntB) - else - unexpected($pred, "invalid sort string") +slice_exec_count_compare(HeadCmp, TailCmps, ExecCountsA, ExecCountsB, Result) :- + ( + ( + HeadCmp = exec_ascend, + CountA = ExecCountsA ^ slice_count, + CountB = ExecCountsB ^ slice_count + ; + HeadCmp = tests_ascend, + CountA = ExecCountsA ^ slice_tests, + CountB = ExecCountsB ^ slice_tests ), - ( if - Result0 = (=), - string.length(SortStrTail) > 0 - then - slice_exec_count_compare(SortStrTail, - ExecCount1, ExecCount2, Result) - else - Result = Result0 - ) + builtin.compare(HeadResult, CountA, CountB) + ; + ( + HeadCmp = exec_descend, + CountA = ExecCountsA ^ slice_count, + CountB = ExecCountsB ^ slice_count + ; + HeadCmp = tests_descend, + CountA = ExecCountsA ^ slice_tests, + CountB = ExecCountsB ^ slice_tests + ), + builtin.compare(HeadResult, CountB, CountA) + ), + ( if + HeadResult = (=), + TailCmps = [HeadTailCmp | TailTailCmps] + then + slice_exec_count_compare(HeadTailCmp, TailTailCmps, + ExecCountsA, ExecCountsB, Result) else - unexpected($pred, "empty sort string") + Result = HeadResult ). -:- pred slice_sort_string_is_valid(string::in) is semidet. +%---------------------------------------------------------------------------% -slice_sort_string_is_valid(Str0) :- - Chrs0 = string.to_char_list(Str0), - ( if Chrs0 = ['z' | ChrsPrime] then - Chrs = ChrsPrime - else - Chrs = Chrs0 - ), - ChrSet = set.list_to_set(Chrs), - set.subset(ChrSet, set.list_to_set(['c', 'C', 't', 'T'])). +:- type slice_cmp + ---> compare_slice_locns + ; compare_slice_counts(slice_count_cmp, list(slice_count_cmp)). + +:- type slice_count_cmp + ---> exec_ascend + ; exec_descend + ; tests_ascend + ; tests_descend. + +:- pred slice_sort_string_is_valid(string::in, slice_cmp::out) is semidet. + +slice_sort_string_is_valid(SortStr, SliceCmp) :- + SortChars = string.to_char_list(SortStr), + ( + SortChars = [], + SliceCmp = compare_slice_locns + ; + SortChars = [HeadChar | TailChars], + slice_sort_char_is_valid(HeadChar, HeadCmp), + list.map(slice_sort_char_is_valid, TailChars, TailCmps), + SliceCmp = compare_slice_counts(HeadCmp, TailCmps) + ). + +:- pred slice_sort_char_is_valid(char::in, slice_count_cmp::out) is semidet. + +slice_sort_char_is_valid(Char, Cmp) :- + ( Char = 'c', Cmp = exec_ascend + ; Char = 'C', Cmp = exec_descend + ; Char = 't', Cmp = tests_ascend + ; Char = 'T', Cmp = tests_descend + ). + +%---------------------------------------------------------------------------% :- func slice_to_label_counts(slice_proc_map) = list(slice_label_count). @@ -677,7 +708,7 @@ read_dice_to_string_no_limit(PassFile, FailFile, SortStr, MaxRow, read_dice_to_string(PassFile, FailFile, SortStr, MaxRow, MaybeMaxPredColumns, MaybeMaxPathColumns, MaybeMaxFileColumns, Module, DiceStr, Problem, !IO) :- - ( if dice_sort_string_is_valid(SortStr) then + ( if dice_sort_string_is_valid(SortStr, DiceCmp) then read_dice(PassFile, FailFile, ReadDiceResult, !IO), ( ReadDiceResult = ok(Dice), @@ -689,8 +720,8 @@ read_dice_to_string(PassFile, FailFile, SortStr, MaxRow, list.filter(dice_label_count_is_for_module(Module), LabelCounts, FilteredLabelCounts) ), - list.sort(dice_label_count_compare(SortStr), FilteredLabelCounts, - SortedLabelCounts), + list.sort(dice_label_count_compare(DiceCmp), + FilteredLabelCounts, SortedLabelCounts), ( if list.take(MaxRow, SortedLabelCounts, Taken) then TopNLabelCounts = Taken else @@ -725,14 +756,15 @@ read_dice_to_string(PassFile, FailFile, SortStr, MaxRow, dice_label_count_is_for_module(Module, dice_label_count(Label, _, _)) :- proc_label_is_for_module(Module, Label). -:- pred dice_label_count_compare(string::in, +:- pred dice_label_count_compare(dice_cmp::in, dice_label_count::in, dice_label_count::in, builtin.comparison_result::out) is det. -dice_label_count_compare(SortStr, LabelCountA, LabelCountB, Result) :- - ( if SortStr = "" then - LabelCountA = dice_label_count(ProcLabelA, PathPortA, CountsA), - LabelCountB = dice_label_count(ProcLabelB, PathPortB, CountsB), +dice_label_count_compare(DiceCmp, LabelCountA, LabelCountB, Result) :- + LabelCountA = dice_label_count(ProcLabelA, PathPortA, ExecCountsA), + LabelCountB = dice_label_count(ProcLabelB, PathPortB, ExecCountsB), + ( + DiceCmp = compare_dice_locns, builtin.compare(ProcLabelResult, ProcLabelA, ProcLabelB), ( ProcLabelResult = (<), @@ -745,7 +777,7 @@ dice_label_count_compare(SortStr, LabelCountA, LabelCountB, Result) :- Result = (<) ; PathPortResult = (=), - builtin.compare(Result, CountsA, CountsB) + builtin.compare(Result, ExecCountsA, ExecCountsB) ; PathPortResult = (>), Result = (>) @@ -754,103 +786,140 @@ dice_label_count_compare(SortStr, LabelCountA, LabelCountB, Result) :- ProcLabelResult = (>), Result = (>) ) - else - dice_exec_count_compare(SortStr, - LabelCountA ^ dlc_counts, LabelCountB ^ dlc_counts, Result) + ; + DiceCmp = compare_dice_counts(HeadCmp, TailCmps), + dice_exec_count_compare(HeadCmp, TailCmps, ExecCountsA, ExecCountsB, + Result) ). -:- pred dice_exec_count_compare(string::in, +:- pred dice_exec_count_compare(dice_count_cmp::in, list(dice_count_cmp)::in, dice_exec_count::in, dice_exec_count::in, builtin.comparison_result::out) is det. -dice_exec_count_compare(SortStr, ExecCount1, ExecCount2, Result) :- - ( if string.first_char(SortStr, C, SortStrTail) then - ( if - ( - C = 'p', - CntA = ExecCount1 ^ pass_count, - CntB = ExecCount2 ^ pass_count - ; - C = 'P', - CntA = ExecCount2 ^ pass_count, - CntB = ExecCount1 ^ pass_count - ; - C = 'f', - CntA = ExecCount1 ^ fail_count, - CntB = ExecCount2 ^ fail_count - ; - C = 'F', - CntA = ExecCount2 ^ fail_count, - CntB = ExecCount1 ^ fail_count - ; - C = 'd', - CntA = int.minus(ExecCount1 ^ pass_count, - ExecCount1 ^ fail_count), - CntB = int.minus(ExecCount2 ^ pass_count, - ExecCount2 ^ fail_count) - ; - C = 'D', - CntA = int.minus(ExecCount1 ^ pass_count, - ExecCount1 ^ fail_count), - CntB = int.minus(ExecCount2 ^ pass_count, - ExecCount2 ^ fail_count) - ) - then - % This is a comparison of two integers. - builtin.compare(Result0, CntA, CntB) - else if - ( - C = 's', - RatioA = suspicion_ratio(ExecCount1 ^ pass_count, - ExecCount1 ^ fail_count), - RatioB = suspicion_ratio(ExecCount2 ^ pass_count, - ExecCount2 ^ fail_count) - ; - C = 'S', - RatioA = suspicion_ratio(ExecCount2 ^ pass_count, - ExecCount2 ^ fail_count), - RatioB = suspicion_ratio(ExecCount1 ^ pass_count, - ExecCount1 ^ fail_count) - ) - then - % This is a comparison of two floats. - builtin.compare(Result0, RatioA, RatioB) - else - unexpected($pred, "invalid sort string") +dice_exec_count_compare(HeadCmp, TailCmps, ExecCountsA, ExecCountsB, Result) :- + % NOTE: counts compare as integers, while rations compare as floats. + % Both the counts and ratios are compared in separate ascending and + % descending blocks, which are identical except for the direction + % of the comparison. + ( + ( + HeadCmp = pass_ascend, + CountA = ExecCountsA ^ pass_count, + CountB = ExecCountsB ^ pass_count + ; + HeadCmp = fail_ascend, + CountA = ExecCountsA ^ fail_count, + CountB = ExecCountsB ^ fail_count + ; + HeadCmp = pass_minus_fail_ascend, + CountA = int.minus(ExecCountsA ^ pass_count, + ExecCountsA ^ fail_count), + CountB = int.minus(ExecCountsB ^ pass_count, + ExecCountsB ^ fail_count) ), - ( if - Result0 = (=), - string.length(SortStrTail) > 0 - then - dice_exec_count_compare(SortStrTail, - ExecCount1, ExecCount2, Result) - else - Result = Result0 - ) + builtin.compare(HeadResult, CountA, CountB) + ; + ( + HeadCmp = pass_descend, + CountA = ExecCountsA ^ pass_count, + CountB = ExecCountsB ^ pass_count + ; + HeadCmp = fail_descend, + CountA = ExecCountsA ^ fail_count, + CountB = ExecCountsB ^ fail_count + ; + HeadCmp = pass_minus_fail_descend, + CountA = int.minus(ExecCountsA ^ pass_count, + ExecCountsA ^ fail_count), + CountB = int.minus(ExecCountsB ^ pass_count, + ExecCountsB ^ fail_count) + ), + builtin.compare(HeadResult, CountB, CountA) + ; + HeadCmp = suspicion_ascend, + RatioA = suspicion_ratio(ExecCountsA ^ pass_count, + ExecCountsA ^ fail_count), + RatioB = suspicion_ratio(ExecCountsB ^ pass_count, + ExecCountsB ^ fail_count), + builtin.compare(HeadResult, RatioA, RatioB) + + ; + HeadCmp = suspicion_descend, + RatioA = suspicion_ratio(ExecCountsA ^ pass_count, + ExecCountsA ^ fail_count), + RatioB = suspicion_ratio(ExecCountsB ^ pass_count, + ExecCountsB ^ fail_count), + builtin.compare(HeadResult, RatioB, RatioA) + ), + ( if + HeadResult = (=), + TailCmps = [HeadTailCmp | TailTailCmps] + then + dice_exec_count_compare(HeadTailCmp, TailTailCmps, + ExecCountsA, ExecCountsB, Result) else - unexpected($pred, "empty sort string") + Result = HeadResult + ). + +%---------------------------------------------------------------------------% + +:- type dice_cmp + ---> compare_dice_locns + ; compare_dice_counts(dice_count_cmp, list(dice_count_cmp)). + +:- type dice_count_cmp + ---> pass_ascend + ; pass_descend + ; fail_ascend + ; fail_descend + ; pass_minus_fail_descend + ; pass_minus_fail_ascend + ; suspicion_ascend + ; suspicion_descend. + +:- pred dice_sort_string_is_valid(string::in, dice_cmp::out) is semidet. + +dice_sort_string_is_valid(SortStr, DiceCmp) :- + SortChars = string.to_char_list(SortStr), + ( + SortChars = [], + DiceCmp = compare_dice_locns + ; + SortChars = [HeadSortChar | TailSortChars], + dice_sort_char_is_valid(HeadSortChar, HeadCmp), + list.map(dice_sort_char_is_valid, TailSortChars, TailCmps), + DiceCmp = compare_dice_counts(HeadCmp, TailCmps) ). -:- pred dice_sort_string_is_valid(string::in) is semidet. +:- pred dice_sort_char_is_valid(char::in, dice_count_cmp::out) is semidet. + +dice_sort_char_is_valid(Char, Cmp) :- + ( Char = 'p', Cmp = pass_ascend + ; Char = 'P', Cmp = pass_descend + ; Char = 'f', Cmp = fail_ascend + ; Char = 'F', Cmp = fail_descend + ; Char = 'd', Cmp = pass_minus_fail_ascend + ; Char = 'D', Cmp = pass_minus_fail_descend + ; Char = 's', Cmp = suspicion_ascend + ; Char = 'S', Cmp = suspicion_descend + ). -dice_sort_string_is_valid(Str) :- - Chrs = string.to_char_list(Str), - ChrSet = set.list_to_set(Chrs), - set.subset(ChrSet, - set.list_to_set(['p', 'P', 'f', 'F', 's', 'S', 'd', 'D'])). +%---------------------------------------------------------------------------% :- func dice_to_label_counts(dice_proc_map) = list(dice_label_count). dice_to_label_counts(DiceProcMap) = LabelCounts :- - map.foldl(append_dice_label_counts, DiceProcMap, [], LabelCounts). + map.foldl(append_dice_label_counts, DiceProcMap, + cord.init, LabelCountsCord), + LabelCounts = cord.list(LabelCountsCord). :- pred append_dice_label_counts(proc_label::in, proc_dice::in, - list(dice_label_count)::in, list(dice_label_count)::out) is det. + cord(dice_label_count)::in, cord(dice_label_count)::out) is det. -append_dice_label_counts(ProcLabel, ProcDice, !LabelCounts) :- +append_dice_label_counts(ProcLabel, ProcDice, !LabelCountsCord) :- map.to_assoc_list(ProcDice, ProcExecCounts), list.map(make_dice_label_count(ProcLabel), ProcExecCounts, NewLabelCounts), - append(!.LabelCounts, NewLabelCounts, !:LabelCounts). + cord.snoc_list(NewLabelCounts, !LabelCountsCord). :- pred make_dice_label_count(proc_label::in, pair(path_port, dice_exec_count)::in, dice_label_count::out) is det. diff --git a/slice/mslice.m b/slice/mslice.m index 4869e49d1..5487c0c70 100644 --- a/slice/mslice.m +++ b/slice/mslice.m @@ -74,7 +74,7 @@ main(!IO) :- string::in, io::di, io::uo) is det. compute_and_output_slice(StdOutStream, OptionTable, FileName, !IO) :- - lookup_string_option(OptionTable, sort, SortStr), + lookup_string_option(OptionTable, sort, SortStr0), lookup_int_option(OptionTable, max_row, MaxRow), lookup_int_option(OptionTable, max_name_column_width, MaxNameColumnWidth), lookup_int_option(OptionTable, max_path_column_width, MaxPathColumnWidth), @@ -95,7 +95,15 @@ compute_and_output_slice(StdOutStream, OptionTable, FileName, !IO) :- else MaybeMaxFileColumnWidth = yes(MaxFileColumnWidth) ), - read_slice_to_string(FileName, SortStr, MaxRow, + % XXX This should be a separate option. + ( if string.contains_char(SortStr0, 'z') then + string.replace_all(SortStr0, "z", "", SortStr), + Zero = do_filter_zero + else + SortStr = SortStr0, + Zero = do_not_filter_zero + ), + read_slice_to_string(FileName, Zero, SortStr, MaxRow, MaybeMaxNameColumnWidth, MaybeMaxPathColumnWidth, MaybeMaxFileColumnWidth, Module, SliceStr, Problem, !IO), ( if Problem = "" then