src/HOL/Tools/Nitpick/nitpick.ML
changeset 48431 e30323bfc93c
parent 48430 366838a5e235
child 48433 a72239723ae8
equal deleted inserted replaced
48430:366838a5e235 48431:e30323bfc93c
     8 signature NITPICK =
     8 signature NITPICK =
     9 sig
     9 sig
    10   type styp = Nitpick_Util.styp
    10   type styp = Nitpick_Util.styp
    11   type term_postprocessor = Nitpick_Model.term_postprocessor
    11   type term_postprocessor = Nitpick_Model.term_postprocessor
    12 
    12 
    13   datatype mode = Auto_Try | Try | Normal
    13   datatype mode = Auto_Try | Try | Normal | TPTP
    14 
    14 
    15   type params =
    15   type params =
    16     {cards_assigns: (typ option * int list) list,
    16     {cards_assigns: (typ option * int list) list,
    17      maxes_assigns: (styp option * int list) list,
    17      maxes_assigns: (styp option * int list) list,
    18      iters_assigns: (styp option * int list) list,
    18      iters_assigns: (styp option * int list) list,
    91 open Nitpick_Kodkod
    91 open Nitpick_Kodkod
    92 open Nitpick_Model
    92 open Nitpick_Model
    93 
    93 
    94 structure KK = Kodkod
    94 structure KK = Kodkod
    95 
    95 
    96 datatype mode = Auto_Try | Try | Normal
    96 datatype mode = Auto_Try | Try | Normal | TPTP
       
    97 
       
    98 fun is_mode_nt mode = (mode = Normal orelse mode = TPTP)
    97 
    99 
    98 type params =
   100 type params =
    99   {cards_assigns: (typ option * int list) list,
   101   {cards_assigns: (typ option * int list) list,
   100    maxes_assigns: (styp option * int list) list,
   102    maxes_assigns: (styp option * int list) list,
   101    iters_assigns: (styp option * int list) list,
   103    iters_assigns: (styp option * int list) list,
   241         o Pretty.chunks o cons (Pretty.str "") o single
   243         o Pretty.chunks o cons (Pretty.str "") o single
   242         o Pretty.mark Isabelle_Markup.hilite
   244         o Pretty.mark Isabelle_Markup.hilite
   243       else
   245       else
   244         print o Pretty.string_of
   246         print o Pretty.string_of
   245     val pprint_a = pprint Output.urgent_message
   247     val pprint_a = pprint Output.urgent_message
   246     fun pprint_n f = () |> mode = Normal ? pprint Output.urgent_message o f
   248     fun pprint_nt f = () |> is_mode_nt mode ? pprint Output.urgent_message o f
   247     fun pprint_v f = () |> verbose ? pprint Output.urgent_message o f
   249     fun pprint_v f = () |> verbose ? pprint Output.urgent_message o f
   248     fun pprint_d f = () |> debug ? pprint tracing o f
   250     fun pprint_d f = () |> debug ? pprint tracing o f
   249     val print = pprint Output.urgent_message o curry Pretty.blk 0 o pstrs
   251     val print = pprint Output.urgent_message o curry Pretty.blk 0 o pstrs
       
   252     fun print_t f = () |> mode = TPTP ? print o f
   250 (*
   253 (*
   251     val print_g = pprint tracing o Pretty.str
   254     val print_g = pprint tracing o Pretty.str
   252 *)
   255 *)
   253     val print_n = pprint_n o K o plazy
   256     val print_nt = pprint_nt o K o plazy
   254     val print_v = pprint_v o K o plazy
   257     val print_v = pprint_v o K o plazy
   255 
   258 
   256     fun check_deadline () =
   259     fun check_deadline () =
   257       if passed_deadline deadline then raise TimeLimit.TimeOut else ()
   260       if passed_deadline deadline then raise TimeLimit.TimeOut else ()
   258 
   261 
   259     val assm_ts = if assms orelse mode <> Normal then assm_ts else []
   262     val assm_ts = if assms orelse mode <> Normal then assm_ts else []
   260     val _ =
   263     val _ =
   261       if step = 0 then
   264       if step = 0 then
   262         print_n (fn () => "Nitpicking formula...")
   265         print_nt (fn () => "Nitpicking formula...")
   263       else
   266       else
   264         pprint_n (fn () => Pretty.chunks (
   267         pprint_nt (fn () => Pretty.chunks (
   265             pretties_for_formulas ctxt ("Nitpicking " ^
   268             pretties_for_formulas ctxt ("Nitpicking " ^
   266                 (if i <> 1 orelse n <> 1 then
   269                 (if i <> 1 orelse n <> 1 then
   267                    "subgoal " ^ string_of_int i ^ " of " ^ string_of_int n
   270                    "subgoal " ^ string_of_int i ^ " of " ^ string_of_int n
   268                  else
   271                  else
   269                    "goal")) [Logic.list_implies (assm_ts, orig_t)]))
   272                    "goal")) [Logic.list_implies (assm_ts, orig_t)]))
   321       preprocess_formulas hol_ctxt assm_ts neg_t
   324       preprocess_formulas hol_ctxt assm_ts neg_t
   322     val got_all_user_axioms =
   325     val got_all_user_axioms =
   323       got_all_mono_user_axioms andalso no_poly_user_axioms
   326       got_all_mono_user_axioms andalso no_poly_user_axioms
   324 
   327 
   325     fun print_wf (x, (gfp, wf)) =
   328     fun print_wf (x, (gfp, wf)) =
   326       pprint_n (fn () => Pretty.blk (0,
   329       pprint_nt (fn () => Pretty.blk (0,
   327           pstrs ("The " ^ (if gfp then "co" else "") ^ "inductive predicate \"")
   330           pstrs ("The " ^ (if gfp then "co" else "") ^ "inductive predicate \"")
   328           @ Syntax.pretty_term ctxt (Const x) ::
   331           @ Syntax.pretty_term ctxt (Const x) ::
   329           pstrs (if wf then
   332           pstrs (if wf then
   330                    "\" was proved well-founded. Nitpick can compute it \
   333                    "\" was proved well-founded. Nitpick can compute it \
   331                    \efficiently."
   334                    \efficiently."
   412         ()
   415         ()
   413     val _ =
   416     val _ =
   414       if mode = Normal andalso
   417       if mode = Normal andalso
   415          exists (fn Type (@{type_name Datatype.node}, _) => true | _ => false)
   418          exists (fn Type (@{type_name Datatype.node}, _) => true | _ => false)
   416                 all_Ts then
   419                 all_Ts then
   417         print_n (K ("Warning: The problem involves directly or indirectly the \
   420         print_nt (K ("Warning: The problem involves directly or indirectly the \
   418                     \internal type " ^ quote @{type_name Datatype.node} ^
   421                      \internal type " ^ quote @{type_name Datatype.node} ^
   419                     ". This type is very Nitpick-unfriendly, and its presence \
   422                      ". This type is very Nitpick-unfriendly, and its presence \
   420                     \usually indicates either a failure of abstraction or a \
   423                      \usually indicates either a failure of abstraction or a \
   421                     \quirk in Nitpick."))
   424                      \quirk in Nitpick."))
   422       else
   425       else
   423         ()
   426         ()
   424     val (mono_Ts, nonmono_Ts) = List.partition is_type_monotonic all_Ts
   427     val (mono_Ts, nonmono_Ts) = List.partition is_type_monotonic all_Ts
   425     val _ =
   428     val _ =
   426       if verbose andalso not unique_scope then
   429       if verbose andalso not unique_scope then
   453       exists (exists (curry (op =) name o shortest_name o fst)
   456       exists (exists (curry (op =) name o shortest_name o fst)
   454               o datatype_constrs hol_ctxt) deep_dataTs
   457               o datatype_constrs hol_ctxt) deep_dataTs
   455     val likely_in_struct_induct_step =
   458     val likely_in_struct_induct_step =
   456       exists is_struct_induct_step (Proof_Context.cases_of ctxt)
   459       exists is_struct_induct_step (Proof_Context.cases_of ctxt)
   457     val _ = if standard andalso likely_in_struct_induct_step then
   460     val _ = if standard andalso likely_in_struct_induct_step then
   458               pprint_n (fn () => Pretty.blk (0,
   461               pprint_nt (fn () => Pretty.blk (0,
   459                   pstrs "Hint: To check that the induction hypothesis is \
   462                   pstrs "Hint: To check that the induction hypothesis is \
   460                         \general enough, try this command: " @
   463                         \general enough, try this command: " @
   461                   [Pretty.mark Isabelle_Markup.sendback (Pretty.blk (0,
   464                   [Pretty.mark Isabelle_Markup.sendback (Pretty.blk (0,
   462                        pstrs ("nitpick [non_std, show_all]")))] @ pstrs "."))
   465                        pstrs ("nitpick [non_std, show_all]")))] @ pstrs "."))
   463             else
   466             else
   473     val actual_sat_solver =
   476     val actual_sat_solver =
   474       if sat_solver <> "smart" then
   477       if sat_solver <> "smart" then
   475         if incremental andalso
   478         if incremental andalso
   476            not (member (op =) (Kodkod_SAT.configured_sat_solvers true)
   479            not (member (op =) (Kodkod_SAT.configured_sat_solvers true)
   477                        sat_solver) then
   480                        sat_solver) then
   478           (print_n (K ("An incremental SAT solver is required: \"SAT4J\" will \
   481           (print_nt (K ("An incremental SAT solver is required: \"SAT4J\" will \
   479                        \be used instead of " ^ quote sat_solver ^ "."));
   482                         \be used instead of " ^ quote sat_solver ^ "."));
   480            "SAT4J")
   483            "SAT4J")
   481         else
   484         else
   482           sat_solver
   485           sat_solver
   483       else
   486       else
   484         Kodkod_SAT.smart_sat_solver_name incremental
   487         Kodkod_SAT.smart_sat_solver_name incremental
   651 
   654 
   652     fun update_checked_problems problems =
   655     fun update_checked_problems problems =
   653       List.app (Unsynchronized.change checked_problems o Option.map o cons
   656       List.app (Unsynchronized.change checked_problems o Option.map o cons
   654                 o nth problems)
   657                 o nth problems)
   655     fun show_kodkod_warning "" = ()
   658     fun show_kodkod_warning "" = ()
   656       | show_kodkod_warning s = print_n (fn () => "Kodkod warning: " ^ s ^ ".")
   659       | show_kodkod_warning s = print_nt (fn () => "Kodkod warning: " ^ s ^ ".")
   657 
   660 
   658     fun print_and_check_model genuine bounds
   661     fun print_and_check_model genuine bounds
   659             ({free_names, sel_names, nonsel_names, rel_table, scope, ...}
   662             ({free_names, sel_names, nonsel_names, rel_table, scope, ...}
   660              : problem_extension) =
   663              : problem_extension) =
   661       let
   664       let
   669           got_all_user_axioms andalso none_true wfs andalso
   672           got_all_user_axioms andalso none_true wfs andalso
   670           sound_finitizes andalso total_consts <> SOME true andalso
   673           sound_finitizes andalso total_consts <> SOME true andalso
   671           codatatypes_ok
   674           codatatypes_ok
   672         fun assms_prop () = Logic.mk_conjunction_list (neg_t :: assm_ts)
   675         fun assms_prop () = Logic.mk_conjunction_list (neg_t :: assm_ts)
   673       in
   676       in
   674         (pprint_a (Pretty.chunks
   677         (print_t (fn () =>
       
   678              "% SZS status " ^
       
   679              (if genuine then
       
   680                 if falsify then "CounterSatisfiable" else "Satisfiable"
       
   681               else
       
   682                 "Unknown") ^ "\n" ^
       
   683              "% SZS output start FiniteModel\n");
       
   684          pprint_a (Pretty.chunks
   675              [Pretty.blk (0,
   685              [Pretty.blk (0,
   676                   (pstrs ((if mode = Auto_Try then "Auto " else "") ^
   686                   (pstrs ((if mode = Auto_Try then "Auto " else "") ^
   677                           "Nitpick found a" ^
   687                           "Nitpick found a" ^
   678                           (if not genuine then " potentially spurious "
   688                           (if not genuine then " potentially spurious "
   679                            else if genuine_means_genuine then " "
   689                            else if genuine_means_genuine then " "
   681                    (case pretties_for_scope scope verbose of
   691                    (case pretties_for_scope scope verbose of
   682                       [] => []
   692                       [] => []
   683                     | pretties => pstrs " for " @ pretties) @
   693                     | pretties => pstrs " for " @ pretties) @
   684                    [Pretty.str ":\n"])),
   694                    [Pretty.str ":\n"])),
   685               Pretty.indent indent_size reconstructed_model]);
   695               Pretty.indent indent_size reconstructed_model]);
       
   696          print_t (fn () => "% SZS output stop\n");
   686          if genuine then
   697          if genuine then
   687            (if check_genuine andalso standard then
   698            (if check_genuine andalso standard then
   688               case prove_hol_model scope tac_timeout free_names sel_names
   699               case prove_hol_model scope tac_timeout free_names sel_names
   689                                    rel_table bounds (assms_prop ()) of
   700                                    rel_table bounds (assms_prop ()) of
   690                 SOME true =>
   701                 SOME true =>
   783           (found_really_genuine, 0, 0, donno)
   794           (found_really_genuine, 0, 0, donno)
   784         else
   795         else
   785           case KK.solve_any_problem debug overlord deadline max_threads
   796           case KK.solve_any_problem debug overlord deadline max_threads
   786                                     max_solutions (map fst problems) of
   797                                     max_solutions (map fst problems) of
   787             KK.JavaNotInstalled =>
   798             KK.JavaNotInstalled =>
   788             (print_n install_java_message;
   799             (print_nt install_java_message;
   789              (found_really_genuine, max_potential, max_genuine, donno + 1))
   800              (found_really_genuine, max_potential, max_genuine, donno + 1))
   790           | KK.JavaTooOld =>
   801           | KK.JavaTooOld =>
   791             (print_n install_java_message;
   802             (print_nt install_java_message;
   792              (found_really_genuine, max_potential, max_genuine, donno + 1))
   803              (found_really_genuine, max_potential, max_genuine, donno + 1))
   793           | KK.KodkodiNotInstalled =>
   804           | KK.KodkodiNotInstalled =>
   794             (print_n install_kodkodi_message;
   805             (print_nt install_kodkodi_message;
   795              (found_really_genuine, max_potential, max_genuine, donno + 1))
   806              (found_really_genuine, max_potential, max_genuine, donno + 1))
   796           | KK.Normal ([], unsat_js, s) =>
   807           | KK.Normal ([], unsat_js, s) =>
   797             (update_checked_problems problems unsat_js; show_kodkod_warning s;
   808             (update_checked_problems problems unsat_js; show_kodkod_warning s;
   798              (found_really_genuine, max_potential, max_genuine, donno))
   809              (found_really_genuine, max_potential, max_genuine, donno))
   799           | KK.Normal (sat_ps, unsat_js, s) =>
   810           | KK.Normal (sat_ps, unsat_js, s) =>
   876     fun run_batch j n scopes (found_really_genuine, max_potential, max_genuine,
   887     fun run_batch j n scopes (found_really_genuine, max_potential, max_genuine,
   877                               donno) =
   888                               donno) =
   878       let
   889       let
   879         val _ =
   890         val _ =
   880           if null scopes then
   891           if null scopes then
   881             print_n (K "The scope specification is inconsistent.")
   892             print_nt (K "The scope specification is inconsistent.")
   882           else if verbose then
   893           else if verbose then
   883             pprint_n (fn () => Pretty.chunks
   894             pprint_nt (fn () => Pretty.chunks
   884                 [Pretty.blk (0,
   895                 [Pretty.blk (0,
   885                      pstrs ((if n > 1 then
   896                      pstrs ((if n > 1 then
   886                                "Batch " ^ string_of_int (j + 1) ^ " of " ^
   897                                "Batch " ^ string_of_int (j + 1) ^ " of " ^
   887                                signed_string_of_int n ^ ": "
   898                                signed_string_of_int n ^ ": "
   888                              else
   899                              else
   924                 List.partition (#unsound o snd) (!generated_problems)
   935                 List.partition (#unsound o snd) (!generated_problems)
   925             in
   936             in
   926               if not (null sound_problems) andalso
   937               if not (null sound_problems) andalso
   927                  forall (KK.is_problem_trivially_false o fst)
   938                  forall (KK.is_problem_trivially_false o fst)
   928                         sound_problems then
   939                         sound_problems then
   929                 print_n (fn () =>
   940                 print_nt (fn () =>
   930                     "Warning: The conjecture " ^
   941                     "Warning: The conjecture " ^
   931                     (if falsify then "either trivially holds"
   942                     (if falsify then "either trivially holds"
   932                      else "is either trivially unsatisfiable") ^
   943                      else "is either trivially unsatisfiable") ^
   933                     " for the given scopes or lies outside Nitpick's supported \
   944                     " for the given scopes or lies outside Nitpick's supported \
   934                     \fragment." ^
   945                     \fragment." ^
   963                    | n =>
   974                    | n =>
   964                      scope_count (do_filter (these (!checked_problems)))
   975                      scope_count (do_filter (these (!checked_problems)))
   965                                  scope = n
   976                                  scope = n
   966                      ? Integer.add 1) (!generated_scopes) 0
   977                      ? Integer.add 1) (!generated_scopes) 0
   967       in
   978       in
       
   979         (if mode = TPTP then "% SZS status Unknown\n" else "") ^
   968         "Nitpick " ^ did_so_and_so ^
   980         "Nitpick " ^ did_so_and_so ^
   969         (if is_some (!checked_problems) andalso total > 0 then
   981         (if is_some (!checked_problems) andalso total > 0 then
   970            " " ^ string_of_int unsat ^ " of " ^ string_of_int total ^ " scope"
   982            " " ^ string_of_int unsat ^ " of " ^ string_of_int total ^ " scope"
   971            ^ plural_s total
   983            ^ plural_s total
   972          else
   984          else
   976     val (skipped, the_scopes) =
   988     val (skipped, the_scopes) =
   977       all_scopes hol_ctxt binarize cards_assigns maxes_assigns iters_assigns
   989       all_scopes hol_ctxt binarize cards_assigns maxes_assigns iters_assigns
   978                  bitss bisim_depths mono_Ts nonmono_Ts deep_dataTs
   990                  bitss bisim_depths mono_Ts nonmono_Ts deep_dataTs
   979                  finitizable_dataTs
   991                  finitizable_dataTs
   980     val _ = if skipped > 0 then
   992     val _ = if skipped > 0 then
   981               print_n (fn () => "Too many scopes. Skipping " ^
   993               print_nt (fn () => "Too many scopes. Skipping " ^
   982                                 string_of_int skipped ^ " scope" ^
   994                                  string_of_int skipped ^ " scope" ^
   983                                 plural_s skipped ^
   995                                  plural_s skipped ^
   984                                 ". (Consider using \"mono\" or \
   996                                  ". (Consider using \"mono\" or \
   985                                 \\"merge_type_vars\" to prevent this.)")
   997                                  \\"merge_type_vars\" to prevent this.)")
   986             else
   998             else
   987               ()
   999               ()
   988     val _ = scopes := the_scopes
  1000     val _ = scopes := the_scopes
   989 
  1001 
   990     fun run_batches _ _ []
  1002     fun run_batches _ _ []
   991                     (found_really_genuine, max_potential, max_genuine, donno) =
  1003                     (found_really_genuine, max_potential, max_genuine, donno) =
   992         if donno > 0 andalso max_genuine > 0 then
  1004         if donno > 0 andalso max_genuine > 0 then
   993           (print_n (fn () => excipit "checked"); unknownN)
  1005           (print_nt (fn () => excipit "checked"); unknownN)
   994         else if max_genuine = original_max_genuine then
  1006         else if max_genuine = original_max_genuine then
   995           if max_potential = original_max_potential then
  1007           if max_potential = original_max_potential then
   996             (print_n (fn () =>
  1008             (print_t (K "% SZS status Unknown\n");
       
  1009              print_nt (fn () =>
   997                  "Nitpick found no " ^ das_wort_model ^ "." ^
  1010                  "Nitpick found no " ^ das_wort_model ^ "." ^
   998                  (if not standard andalso likely_in_struct_induct_step then
  1011                  (if not standard andalso likely_in_struct_induct_step then
   999                     " This suggests that the induction hypothesis might be \
  1012                     " This suggests that the induction hypothesis might be \
  1000                     \general enough to prove this subgoal."
  1013                     \general enough to prove this subgoal."
  1001                   else
  1014                   else
  1002                     "")); if skipped > 0 then unknownN else noneN)
  1015                     "")); if skipped > 0 then unknownN else noneN)
  1003           else
  1016           else
  1004             (print_n (fn () =>
  1017             (print_nt (fn () =>
  1005                  excipit ("could not find a" ^
  1018                  excipit ("could not find a" ^
  1006                           (if max_genuine = 1 then
  1019                           (if max_genuine = 1 then
  1007                              " better " ^ das_wort_model ^ "."
  1020                              " better " ^ das_wort_model ^ "."
  1008                            else
  1021                            else
  1009                              "ny better " ^ das_wort_model ^ "s.") ^
  1022                              "ny better " ^ das_wort_model ^ "s.") ^
  1021     val batches = batch_list batch_size (!scopes)
  1034     val batches = batch_list batch_size (!scopes)
  1022     val outcome_code =
  1035     val outcome_code =
  1023       run_batches 0 (length batches) batches
  1036       run_batches 0 (length batches) batches
  1024                   (false, max_potential, max_genuine, 0)
  1037                   (false, max_potential, max_genuine, 0)
  1025       handle TimeLimit.TimeOut =>
  1038       handle TimeLimit.TimeOut =>
  1026              (print_n (fn () => excipit "ran out of time after checking");
  1039              (print_nt (fn () => excipit "ran out of time after checking");
  1027               if !met_potential > 0 then potentialN else unknownN)
  1040               if !met_potential > 0 then potentialN else unknownN)
  1028     val _ = print_v (fn () =>
  1041     val _ = print_v (fn () =>
  1029                 "Total time: " ^ string_from_time (Timer.checkRealTimer timer) ^
  1042                 "Total time: " ^ string_from_time (Timer.checkRealTimer timer) ^
  1030                 ".")
  1043                 ".")
  1031   in (outcome_code, !state_ref) end
  1044   in (outcome_code, !state_ref) end
  1033 (* Give the inner timeout a chance. *)
  1046 (* Give the inner timeout a chance. *)
  1034 val timeout_bonus = seconds 1.0
  1047 val timeout_bonus = seconds 1.0
  1035 
  1048 
  1036 fun pick_nits_in_term state (params as {debug, timeout, expect, ...}) mode i n
  1049 fun pick_nits_in_term state (params as {debug, timeout, expect, ...}) mode i n
  1037                       step subst assm_ts orig_t =
  1050                       step subst assm_ts orig_t =
  1038   let val print_n = if mode = Normal then Output.urgent_message else K () in
  1051   let val print_nt = if is_mode_nt mode then Output.urgent_message else K () in
  1039     if getenv "KODKODI" = "" then
  1052     if getenv "KODKODI" = "" then
  1040       (* The "expect" argument is deliberately ignored if Kodkodi is missing so
  1053       (* The "expect" argument is deliberately ignored if Kodkodi is missing so
  1041          that the "Nitpick_Examples" can be processed on any machine. *)
  1054          that the "Nitpick_Examples" can be processed on any machine. *)
  1042       (print_n (Pretty.string_of (plazy install_kodkodi_message));
  1055       (print_nt (Pretty.string_of (plazy install_kodkodi_message));
  1043        ("no_kodkodi", state))
  1056        ("no_kodkodi", state))
  1044     else
  1057     else
  1045       let
  1058       let
  1046         val unknown_outcome = (unknownN, state)
  1059         val unknown_outcome = (unknownN, state)
  1047         val deadline = Option.map (curry Time.+ (Time.now ())) timeout
  1060         val deadline = Option.map (curry Time.+ (Time.now ())) timeout
  1049           time_limit (if debug orelse is_none timeout then NONE
  1062           time_limit (if debug orelse is_none timeout then NONE
  1050                       else SOME (Time.+ (the timeout, timeout_bonus)))
  1063                       else SOME (Time.+ (the timeout, timeout_bonus)))
  1051               (pick_them_nits_in_term deadline state params mode i n step subst
  1064               (pick_them_nits_in_term deadline state params mode i n step subst
  1052                                       assm_ts) orig_t
  1065                                       assm_ts) orig_t
  1053           handle TOO_LARGE (_, details) =>
  1066           handle TOO_LARGE (_, details) =>
  1054                  (print_n ("Limit reached: " ^ details ^ "."); unknown_outcome)
  1067                  (print_nt ("Limit reached: " ^ details ^ "."); unknown_outcome)
  1055                | TOO_SMALL (_, details) =>
  1068                | TOO_SMALL (_, details) =>
  1056                  (print_n ("Limit reached: " ^ details ^ "."); unknown_outcome)
  1069                  (print_nt ("Limit reached: " ^ details ^ "."); unknown_outcome)
  1057                | Kodkod.SYNTAX (_, details) =>
  1070                | Kodkod.SYNTAX (_, details) =>
  1058                  (print_n ("Malformed Kodkodi output: " ^ details ^ ".");
  1071                  (print_nt ("Malformed Kodkodi output: " ^ details ^ ".");
  1059                   unknown_outcome)
  1072                   unknown_outcome)
  1060                | TimeLimit.TimeOut =>
  1073                | TimeLimit.TimeOut =>
  1061                  (print_n "Nitpick ran out of time."; unknown_outcome)
  1074                  (print_nt "Nitpick ran out of time."; unknown_outcome)
  1062       in
  1075       in
  1063         if expect = "" orelse outcome_code = expect then outcome
  1076         if expect = "" orelse outcome_code = expect then outcome
  1064         else error ("Unexpected outcome: " ^ quote outcome_code ^ ".")
  1077         else error ("Unexpected outcome: " ^ quote outcome_code ^ ".")
  1065       end
  1078       end
  1066   end
  1079   end