src/HOL/Library/code_test.ML
author haftmann
Wed Jul 18 20:51:21 2018 +0200 (11 months ago)
changeset 68658 16cc1161ad7f
parent 67330 2505cabfc515
child 69593 3dda49e08b9d
permissions -rw-r--r--
tuned equation
wenzelm@59720
     1
(*  Title:      HOL/Library/code_test.ML
wenzelm@64577
     2
    Author:     Andreas Lochbihler, ETH Z├╝rich
Andreas@58039
     3
wenzelm@64577
     4
Test infrastructure for the code generator.
Andreas@58039
     5
*)
Andreas@58039
     6
wenzelm@64577
     7
signature CODE_TEST =
wenzelm@64577
     8
sig
wenzelm@64577
     9
  val add_driver:
wenzelm@64577
    10
    string * ((Proof.context -> (string * string) list * string -> Path.T -> string) * string) ->
wenzelm@64577
    11
    theory -> theory
wenzelm@64577
    12
  val overlord: bool Config.T
wenzelm@64577
    13
  val successN: string
wenzelm@64577
    14
  val failureN: string
wenzelm@64577
    15
  val start_markerN: string
wenzelm@64577
    16
  val end_markerN: string
wenzelm@64577
    17
  val test_terms: Proof.context -> term list -> string -> unit
wenzelm@64578
    18
  val test_targets: Proof.context -> term list -> string list -> unit
wenzelm@64580
    19
  val test_code_cmd: string list -> string list -> Proof.context -> unit
wenzelm@64577
    20
  val eval_term: string -> Proof.context -> term -> term
wenzelm@64581
    21
  val evaluate:
wenzelm@64577
    22
   (theory -> Path.T -> string list -> string ->
wenzelm@64582
    23
     {files: (Path.T * string) list,
wenzelm@64582
    24
       compile_cmd: string option,
wenzelm@64582
    25
       run_cmd: string,
wenzelm@64582
    26
       mk_code_file: string -> Path.T}) -> (string * string) option -> string -> theory ->
wenzelm@64582
    27
     (string * string) list * string -> Path.T -> string
wenzelm@64577
    28
  val evaluate_in_polyml: Proof.context -> (string * string) list * string -> Path.T -> string
wenzelm@64577
    29
  val evaluate_in_mlton: Proof.context -> (string * string) list * string -> Path.T -> string
wenzelm@64577
    30
  val evaluate_in_smlnj: Proof.context -> (string * string) list * string -> Path.T -> string
wenzelm@64577
    31
  val evaluate_in_ocaml: Proof.context -> (string * string) list * string -> Path.T -> string
wenzelm@64577
    32
  val ghc_options: string Config.T
wenzelm@64577
    33
  val evaluate_in_ghc: Proof.context -> (string * string) list * string -> Path.T -> string
wenzelm@64577
    34
  val evaluate_in_scala: Proof.context -> (string * string) list * string -> Path.T -> string
Andreas@66284
    35
  val target_Scala: string
Andreas@66284
    36
  val target_Haskell: string
Andreas@58039
    37
end
Andreas@58039
    38
wenzelm@64577
    39
structure Code_Test: CODE_TEST =
wenzelm@64577
    40
struct
Andreas@58039
    41
Andreas@58039
    42
(* convert a list of terms into nested tuples and back *)
wenzelm@64577
    43
Andreas@58039
    44
fun mk_tuples [] = @{term "()"}
Andreas@58039
    45
  | mk_tuples [t] = t
Andreas@58039
    46
  | mk_tuples (t :: ts) = HOLogic.mk_prod (t, mk_tuples ts)
Andreas@58039
    47
Andreas@58039
    48
fun dest_tuples (Const (@{const_name Pair}, _) $ l $ r) = l :: dest_tuples r
Andreas@58039
    49
  | dest_tuples t = [t]
Andreas@58039
    50
Andreas@58039
    51
Andreas@58039
    52
fun last_field sep str =
Andreas@58039
    53
  let
wenzelm@64577
    54
    val n = size sep
wenzelm@64577
    55
    val len = size str
Andreas@58039
    56
    fun find i =
Andreas@58039
    57
      if i < 0 then NONE
Andreas@58039
    58
      else if String.substring (str, i, n) = sep then SOME i
wenzelm@64577
    59
      else find (i - 1)
Andreas@58039
    60
  in
Andreas@58039
    61
    (case find (len - n) of
Andreas@58039
    62
      NONE => NONE
Andreas@58039
    63
    | SOME i => SOME (String.substring (str, 0, i), String.extract (str, i + n, NONE)))
wenzelm@64577
    64
  end
Andreas@58039
    65
Andreas@58039
    66
fun split_first_last start stop s =
wenzelm@64577
    67
  (case first_field start s of
wenzelm@64577
    68
    NONE => NONE
wenzelm@64577
    69
  | SOME (initial, rest) =>
wenzelm@64577
    70
      (case last_field stop rest of
wenzelm@64577
    71
        NONE => NONE
wenzelm@64577
    72
      | SOME (middle, tail) => SOME (initial, middle, tail)))
Andreas@58039
    73
wenzelm@64577
    74
wenzelm@64577
    75
(* data slot for drivers *)
Andreas@58039
    76
Andreas@58039
    77
structure Drivers = Theory_Data
Andreas@58039
    78
(
wenzelm@64577
    79
  type T =
wenzelm@64577
    80
    (string * ((Proof.context -> (string * string) list * string -> Path.T -> string) * string)) list
wenzelm@64577
    81
  val empty = []
wenzelm@64577
    82
  val extend = I
wenzelm@64577
    83
  fun merge data : T = AList.merge (op =) (K true) data
Andreas@58039
    84
)
Andreas@58039
    85
wenzelm@64577
    86
val add_driver = Drivers.map o AList.update (op =)
wenzelm@64577
    87
val get_driver = AList.lookup (op =) o Drivers.get
Andreas@58039
    88
Andreas@58039
    89
(*
Andreas@58039
    90
  Test drivers must produce output of the following format:
wenzelm@64577
    91
Andreas@58039
    92
  The start of the relevant data is marked with start_markerN,
Andreas@58039
    93
  its end with end_markerN.
Andreas@58039
    94
Andreas@58039
    95
  Between these two markers, every line corresponds to one test.
Andreas@58039
    96
  Lines of successful tests start with successN, failures start with failureN.
Andreas@58039
    97
  The failure failureN may continue with the YXML encoding of the evaluated term.
Andreas@58039
    98
  There must not be any additional whitespace in between.
Andreas@58039
    99
*)
Andreas@58039
   100
wenzelm@64577
   101
wenzelm@64577
   102
(* parsing of results *)
Andreas@58039
   103
Andreas@58039
   104
val successN = "True"
Andreas@58039
   105
val failureN = "False"
Andreas@58039
   106
val start_markerN = "*@*Isabelle/Code_Test-start*@*"
Andreas@58039
   107
val end_markerN = "*@*Isabelle/Code_Test-end*@*"
Andreas@58039
   108
Andreas@58039
   109
fun parse_line line =
Andreas@58039
   110
  if String.isPrefix successN line then (true, NONE)
wenzelm@64577
   111
  else if String.isPrefix failureN line then (false,
Andreas@58039
   112
    if size line > size failureN then
Andreas@58039
   113
      String.extract (line, size failureN, NONE)
Andreas@58039
   114
      |> YXML.parse_body
Andreas@58039
   115
      |> Term_XML.Decode.term
Andreas@58039
   116
      |> dest_tuples
Andreas@58039
   117
      |> SOME
Andreas@58039
   118
    else NONE)
Andreas@58039
   119
  else raise Fail ("Cannot parse result of evaluation:\n" ^ line)
Andreas@58039
   120
Andreas@58039
   121
fun parse_result target out =
wenzelm@64577
   122
  (case split_first_last start_markerN end_markerN out of
wenzelm@64577
   123
    NONE => error ("Evaluation failed for " ^ target ^ "!\nBash output:\n" ^ out)
wenzelm@65904
   124
  | SOME (_, middle, _) => middle |> trim_split_lines |> map parse_line)
Andreas@58039
   125
wenzelm@64577
   126
wenzelm@64577
   127
(* pretty printing of test results *)
Andreas@58039
   128
Andreas@58039
   129
fun pretty_eval _ NONE _ = []
wenzelm@64577
   130
  | pretty_eval ctxt (SOME evals) ts =
wenzelm@64577
   131
      [Pretty.fbrk,
wenzelm@64577
   132
       Pretty.big_list "Evaluated terms"
wenzelm@64577
   133
         (map (fn (t, eval) => Pretty.block
wenzelm@64577
   134
           [Syntax.pretty_term ctxt t, Pretty.brk 1, Pretty.str "=", Pretty.brk 1,
wenzelm@64577
   135
            Syntax.pretty_term ctxt eval])
wenzelm@64577
   136
         (ts ~~ evals))]
Andreas@58039
   137
Andreas@58039
   138
fun pretty_failure ctxt target (((_, evals), query), eval_ts) =
wenzelm@60022
   139
  Pretty.block (Pretty.text ("Test in " ^ target ^ " failed for")
wenzelm@60022
   140
    @ [Pretty.brk 1, Pretty.quote (Syntax.pretty_term ctxt query)]
Andreas@58039
   141
    @ pretty_eval ctxt evals eval_ts)
Andreas@58039
   142
Andreas@58039
   143
fun pretty_failures ctxt target failures =
Andreas@58039
   144
  Pretty.blk (0, Pretty.fbreaks (map (pretty_failure ctxt target) failures))
Andreas@58039
   145
Andreas@58039
   146
wenzelm@64577
   147
(* driver invocation *)
wenzelm@64577
   148
wenzelm@64577
   149
val overlord = Attrib.setup_config_bool @{binding "code_test_overlord"} (K false)
Andreas@58039
   150
Andreas@58039
   151
fun with_overlord_dir name f =
Andreas@58039
   152
  let
wenzelm@64577
   153
    val path =
wenzelm@64577
   154
      Path.append (Path.explode "$ISABELLE_HOME_USER") (Path.basic (name ^ serial_string ()))
wenzelm@64577
   155
    val _ = Isabelle_System.mkdirs path
wenzelm@64577
   156
  in Exn.release (Exn.capture f path) end
Andreas@58039
   157
Andreas@58039
   158
fun dynamic_value_strict ctxt t compiler =
Andreas@58039
   159
  let
Andreas@58039
   160
    val thy = Proof_Context.theory_of ctxt
wenzelm@64577
   161
    val (driver, target) =
wenzelm@64577
   162
      (case get_driver thy compiler of
wenzelm@64577
   163
        NONE => error ("No driver for target " ^ compiler)
wenzelm@64577
   164
      | SOME f => f)
Andreas@58039
   165
    val debug = Config.get (Proof_Context.init_global thy) overlord
Andreas@58039
   166
    val with_dir = if debug then with_overlord_dir else Isabelle_System.with_tmp_dir
Andreas@58039
   167
    fun evaluate f = with_dir "Code_Test" (driver ctxt f) |> parse_result compiler
Andreas@58039
   168
    fun evaluator program _ vs_ty deps =
wenzelm@64577
   169
      Exn.interruptible_capture evaluate
haftmann@64957
   170
        (Code_Target.compilation_text ctxt target program deps true vs_ty)
wenzelm@64578
   171
    fun postproc f = map (apsnd (Option.map (map f)))
wenzelm@64577
   172
  in Exn.release (Code_Thingol.dynamic_value ctxt (Exn.map_res o postproc) evaluator t) end
Andreas@58039
   173
wenzelm@64577
   174
wenzelm@64577
   175
(* term preprocessing *)
Andreas@58039
   176
Andreas@58039
   177
fun add_eval (Const (@{const_name Trueprop}, _) $ t) = add_eval t
Andreas@58039
   178
  | add_eval (Const (@{const_name "HOL.eq"}, _) $ lhs $ rhs) = (fn acc =>
wenzelm@64577
   179
      acc
wenzelm@64577
   180
      |> add_eval rhs
wenzelm@64577
   181
      |> add_eval lhs
wenzelm@64577
   182
      |> cons rhs
wenzelm@64577
   183
      |> cons lhs)
Andreas@58039
   184
  | add_eval (Const (@{const_name "Not"}, _) $ t) = add_eval t
Andreas@58039
   185
  | add_eval (Const (@{const_name "Orderings.ord_class.less_eq"}, _) $ lhs $ rhs) = (fn acc =>
wenzelm@64577
   186
      lhs :: rhs :: acc)
Andreas@58039
   187
  | add_eval (Const (@{const_name "Orderings.ord_class.less"}, _) $ lhs $ rhs) = (fn acc =>
wenzelm@64577
   188
      lhs :: rhs :: acc)
Andreas@58039
   189
  | add_eval _ = I
Andreas@58039
   190
Andreas@58039
   191
fun mk_term_of [] = @{term "None :: (unit \<Rightarrow> yxml_of_term) option"}
Andreas@58039
   192
  | mk_term_of ts =
wenzelm@64577
   193
      let
wenzelm@64577
   194
        val tuple = mk_tuples ts
wenzelm@64577
   195
        val T = fastype_of tuple
wenzelm@64577
   196
      in
wenzelm@64577
   197
        @{term "Some :: (unit \<Rightarrow> yxml_of_term) \<Rightarrow> (unit \<Rightarrow> yxml_of_term) option"} $
wenzelm@64577
   198
          (absdummy @{typ unit} (@{const yxml_string_of_term} $
wenzelm@64577
   199
            (Const (@{const_name Code_Evaluation.term_of}, T --> @{typ term}) $ tuple)))
wenzelm@64577
   200
      end
Andreas@58039
   201
Andreas@58039
   202
fun test_terms ctxt ts target =
Andreas@58039
   203
  let
Andreas@58039
   204
    val thy = Proof_Context.theory_of ctxt
Andreas@58039
   205
Andreas@58039
   206
    fun term_of t = Sign.of_sort thy (fastype_of t, @{sort term_of})
Andreas@58039
   207
wenzelm@64577
   208
    fun ensure_bool t =
wenzelm@64577
   209
      (case fastype_of t of
wenzelm@64577
   210
        @{typ bool} => ()
wenzelm@64579
   211
      | _ =>
wenzelm@64579
   212
        error (Pretty.string_of
wenzelm@64579
   213
          (Pretty.block [Pretty.str "Test case not of type bool:", Pretty.brk 1,
wenzelm@64579
   214
            Syntax.pretty_term ctxt t])))
Andreas@58039
   215
wenzelm@64578
   216
    val _ = List.app ensure_bool ts
Andreas@58039
   217
Andreas@58039
   218
    val evals = map (fn t => filter term_of (add_eval t [])) ts
Andreas@58039
   219
    val eval = map mk_term_of evals
Andreas@58039
   220
wenzelm@64578
   221
    val t =
wenzelm@64578
   222
      HOLogic.mk_list @{typ "bool \<times> (unit \<Rightarrow> yxml_of_term) option"}
wenzelm@64578
   223
        (map HOLogic.mk_prod (ts ~~ eval))
Andreas@58039
   224
wenzelm@64577
   225
    val result = dynamic_value_strict ctxt t target
Andreas@58039
   226
Andreas@58039
   227
    val failed =
Andreas@58039
   228
      filter_out (fst o fst o fst) (result ~~ ts ~~ evals)
wenzelm@64577
   229
      handle ListPair.UnequalLengths =>
Andreas@58039
   230
        error ("Evaluation failed!\nWrong number of test results: " ^ Int.toString (length result))
Andreas@58039
   231
  in
wenzelm@64578
   232
    (case failed of [] =>
wenzelm@64578
   233
      ()
wenzelm@64578
   234
    | _ => error (Pretty.string_of (pretty_failures ctxt target failed)))
Andreas@58039
   235
  end
Andreas@58039
   236
wenzelm@64578
   237
fun test_targets ctxt = List.app o test_terms ctxt
Andreas@58039
   238
wenzelm@64579
   239
fun pretty_free ctxt = Syntax.pretty_term ctxt o Free
wenzelm@64579
   240
wenzelm@64580
   241
fun test_code_cmd raw_ts targets ctxt =
Andreas@58039
   242
  let
wenzelm@64577
   243
    val ts = Syntax.read_terms ctxt raw_ts
wenzelm@64579
   244
    val frees = fold Term.add_frees ts []
wenzelm@64578
   245
    val _ =
wenzelm@64578
   246
      if null frees then ()
wenzelm@64579
   247
      else error (Pretty.string_of
wenzelm@64579
   248
        (Pretty.block (Pretty.str "Terms contain free variables:" :: Pretty.brk 1 ::
wenzelm@64579
   249
          Pretty.commas (map (pretty_free ctxt) frees))))
wenzelm@64578
   250
  in test_targets ctxt ts targets end
Andreas@58039
   251
Andreas@58348
   252
fun eval_term target ctxt t =
Andreas@58348
   253
  let
wenzelm@64579
   254
    val frees = Term.add_frees t []
wenzelm@64578
   255
    val _ =
wenzelm@64578
   256
      if null frees then ()
wenzelm@64579
   257
      else
wenzelm@64579
   258
        error (Pretty.string_of
wenzelm@64579
   259
          (Pretty.block (Pretty.str "Term contains free variables:" :: Pretty.brk 1 ::
wenzelm@64579
   260
            Pretty.commas (map (pretty_free ctxt) frees))))
Andreas@58039
   261
wenzelm@64579
   262
    val T = fastype_of t
wenzelm@64578
   263
    val _ =
wenzelm@64579
   264
      if Sign.of_sort (Proof_Context.theory_of ctxt) (T, @{sort term_of}) then ()
wenzelm@64579
   265
      else error ("Type " ^ Syntax.string_of_typ ctxt T ^
wenzelm@64579
   266
       " of term not of sort " ^ Syntax.string_of_sort ctxt @{sort term_of})
Andreas@58039
   267
wenzelm@64578
   268
    val t' =
wenzelm@64578
   269
      HOLogic.mk_list @{typ "bool \<times> (unit \<Rightarrow> yxml_of_term) option"}
wenzelm@64578
   270
        [HOLogic.mk_prod (@{term "False"}, mk_term_of [t])]
Andreas@58039
   271
wenzelm@64577
   272
    val result = dynamic_value_strict ctxt t' target
wenzelm@64577
   273
  in (case result of [(_, SOME [t])] => t | _ => error "Evaluation failed") end
Andreas@58039
   274
wenzelm@64577
   275
wenzelm@64577
   276
(* generic driver *)
Andreas@58039
   277
wenzelm@64582
   278
fun evaluate mk_driver opt_env_var compilerN ctxt (code_files, value_name) =
Andreas@58039
   279
  let
wenzelm@64577
   280
    val _ =
wenzelm@64582
   281
      (case opt_env_var of
wenzelm@64582
   282
        NONE => ()
wenzelm@64582
   283
      | SOME (env_var, env_var_dest) =>
wenzelm@64582
   284
          (case getenv env_var of
wenzelm@64582
   285
            "" =>
wenzelm@64582
   286
              error (Pretty.string_of (Pretty.para
wenzelm@64582
   287
                ("Environment variable " ^ env_var ^ " is not set. To test code generation with " ^
wenzelm@64582
   288
                  compilerN ^ ", set this variable to your " ^ env_var_dest ^
wenzelm@64582
   289
                  " in the $ISABELLE_HOME_USER/etc/settings file.")))
wenzelm@64582
   290
          | _ => ()))
Andreas@58039
   291
Andreas@58039
   292
    fun compile NONE = ()
Andreas@58039
   293
      | compile (SOME cmd) =
wenzelm@64577
   294
          let
wenzelm@64577
   295
            val (out, ret) = Isabelle_System.bash_output cmd
wenzelm@64577
   296
          in
wenzelm@64578
   297
            if ret = 0 then ()
wenzelm@64578
   298
            else error ("Compilation with " ^ compilerN ^ " failed:\n" ^ cmd ^ "\n" ^ out)
wenzelm@64577
   299
          end
Andreas@58039
   300
wenzelm@64577
   301
    fun run path =
Andreas@58039
   302
      let
Andreas@58039
   303
        val modules = map fst code_files
wenzelm@64577
   304
        val {files, compile_cmd, run_cmd, mk_code_file} = mk_driver ctxt path modules value_name
Andreas@58039
   305
wenzelm@64578
   306
        val _ = List.app (fn (name, code) => File.write (mk_code_file name) code) code_files
wenzelm@64578
   307
        val _ = List.app (fn (name, content) => File.write name content) files
Andreas@58039
   308
Andreas@58039
   309
        val _ = compile compile_cmd
Andreas@58039
   310
Andreas@58039
   311
        val (out, res) = Isabelle_System.bash_output run_cmd
wenzelm@64578
   312
        val _ =
wenzelm@64578
   313
          if res = 0 then ()
wenzelm@64578
   314
          else error ("Evaluation for " ^ compilerN ^ " terminated with error code " ^
wenzelm@64578
   315
            Int.toString res ^ "\nBash output:\n" ^ out)
wenzelm@64577
   316
      in out end
wenzelm@64577
   317
  in run end
Andreas@58039
   318
wenzelm@64577
   319
wenzelm@64577
   320
(* driver for PolyML *)
Andreas@58039
   321
wenzelm@64577
   322
val polymlN = "PolyML"
Andreas@58039
   323
Andreas@58039
   324
fun mk_driver_polyml _ path _ value_name =
Andreas@58039
   325
  let
Andreas@58039
   326
    val generatedN = "generated.sml"
Andreas@58039
   327
    val driverN = "driver.sml"
Andreas@58039
   328
Andreas@58039
   329
    val code_path = Path.append path (Path.basic generatedN)
Andreas@58039
   330
    val driver_path = Path.append path (Path.basic driverN)
wenzelm@64577
   331
    val driver =
Andreas@58039
   332
      "fun main prog_name = \n" ^
Andreas@58039
   333
      "  let\n" ^
wenzelm@64577
   334
      "    fun format_term NONE = \"\"\n" ^
Andreas@58039
   335
      "      | format_term (SOME t) = t ();\n" ^
Andreas@58039
   336
      "    fun format (true, _) = \"" ^ successN ^ "\\n\"\n" ^
Andreas@58039
   337
      "      | format (false, to) = \"" ^ failureN ^ "\" ^ format_term to ^ \"\\n\";\n" ^
Andreas@58039
   338
      "    val result = " ^ value_name ^ " ();\n" ^
Andreas@58039
   339
      "    val _ = print \"" ^ start_markerN ^ "\";\n" ^
Andreas@58039
   340
      "    val _ = map (print o format) result;\n" ^
Andreas@58039
   341
      "    val _ = print \"" ^ end_markerN ^ "\";\n" ^
Andreas@58039
   342
      "  in\n" ^
Andreas@58039
   343
      "    ()\n" ^
Andreas@58039
   344
      "  end;\n"
Andreas@58039
   345
    val cmd =
wenzelm@67101
   346
      "\"$POLYML_EXE\" --use " ^ Bash.string (File.platform_path code_path) ^
wenzelm@65898
   347
      " --use " ^ Bash.string (File.platform_path driver_path) ^
wenzelm@65898
   348
      " --eval " ^ Bash.string "main ()"
Andreas@58039
   349
  in
Andreas@58039
   350
    {files = [(driver_path, driver)], compile_cmd = NONE, run_cmd = cmd, mk_code_file = K code_path}
Andreas@58039
   351
  end
Andreas@58039
   352
wenzelm@64582
   353
fun evaluate_in_polyml ctxt = evaluate mk_driver_polyml NONE polymlN ctxt
Andreas@58039
   354
wenzelm@64577
   355
wenzelm@64577
   356
(* driver for mlton *)
Andreas@58039
   357
Andreas@58039
   358
val mltonN = "MLton"
Andreas@58039
   359
val ISABELLE_MLTON = "ISABELLE_MLTON"
Andreas@58039
   360
Andreas@58039
   361
fun mk_driver_mlton _ path _ value_name =
Andreas@58039
   362
  let
Andreas@58039
   363
    val generatedN = "generated.sml"
Andreas@58039
   364
    val driverN = "driver.sml"
Andreas@58039
   365
    val projectN = "test"
Andreas@58039
   366
    val ml_basisN = projectN ^ ".mlb"
Andreas@58039
   367
Andreas@58039
   368
    val code_path = Path.append path (Path.basic generatedN)
Andreas@58039
   369
    val driver_path = Path.append path (Path.basic driverN)
Andreas@58039
   370
    val ml_basis_path = Path.append path (Path.basic ml_basisN)
wenzelm@64577
   371
    val driver =
wenzelm@64577
   372
      "fun format_term NONE = \"\"\n" ^
Andreas@58039
   373
      "  | format_term (SOME t) = t ();\n" ^
Andreas@58039
   374
      "fun format (true, _) = \"" ^ successN ^ "\\n\"\n" ^
Andreas@58039
   375
      "  | format (false, to) = \"" ^ failureN ^ "\" ^ format_term to ^ \"\\n\";\n" ^
Andreas@58039
   376
      "val result = " ^ value_name ^ " ();\n" ^
Andreas@58039
   377
      "val _ = print \"" ^ start_markerN ^ "\";\n" ^
Andreas@58039
   378
      "val _ = map (print o format) result;\n" ^
Andreas@58039
   379
      "val _ = print \"" ^ end_markerN ^ "\";\n"
Andreas@58039
   380
    val ml_basis =
Andreas@58039
   381
      "$(SML_LIB)/basis/basis.mlb\n" ^
Andreas@58039
   382
      generatedN ^ "\n" ^
Andreas@58039
   383
      driverN
Andreas@58039
   384
Andreas@58039
   385
    val compile_cmd =
wenzelm@62549
   386
      File.bash_path (Path.variable ISABELLE_MLTON) ^
wenzelm@62549
   387
      " -default-type intinf " ^ File.bash_path ml_basis_path
wenzelm@62549
   388
    val run_cmd = File.bash_path (Path.append path (Path.basic projectN))
Andreas@58039
   389
  in
Andreas@58039
   390
    {files = [(driver_path, driver), (ml_basis_path, ml_basis)],
Andreas@58039
   391
     compile_cmd = SOME compile_cmd, run_cmd = run_cmd, mk_code_file = K code_path}
Andreas@58039
   392
  end
Andreas@58039
   393
wenzelm@58832
   394
fun evaluate_in_mlton ctxt =
wenzelm@64582
   395
  evaluate mk_driver_mlton (SOME (ISABELLE_MLTON, "MLton executable")) mltonN ctxt
Andreas@58039
   396
wenzelm@64577
   397
wenzelm@64577
   398
(* driver for SML/NJ *)
Andreas@58039
   399
Andreas@58039
   400
val smlnjN = "SMLNJ"
Andreas@58039
   401
val ISABELLE_SMLNJ = "ISABELLE_SMLNJ"
Andreas@58039
   402
Andreas@58039
   403
fun mk_driver_smlnj _ path _ value_name =
Andreas@58039
   404
  let
Andreas@58039
   405
    val generatedN = "generated.sml"
Andreas@58039
   406
    val driverN = "driver.sml"
Andreas@58039
   407
Andreas@58039
   408
    val code_path = Path.append path (Path.basic generatedN)
Andreas@58039
   409
    val driver_path = Path.append path (Path.basic driverN)
wenzelm@64577
   410
    val driver =
Andreas@58039
   411
      "structure Test = struct\n" ^
Andreas@58039
   412
      "fun main prog_name =\n" ^
Andreas@58039
   413
      "  let\n" ^
wenzelm@64577
   414
      "    fun format_term NONE = \"\"\n" ^
Andreas@58039
   415
      "      | format_term (SOME t) = t ();\n" ^
Andreas@58039
   416
      "    fun format (true, _) = \"" ^ successN ^ "\\n\"\n" ^
Andreas@58039
   417
      "      | format (false, to) = \"" ^ failureN ^ "\" ^ format_term to ^ \"\\n\";\n" ^
Andreas@58039
   418
      "    val result = " ^ value_name ^ " ();\n" ^
Andreas@58039
   419
      "    val _ = print \"" ^ start_markerN ^ "\";\n" ^
Andreas@58039
   420
      "    val _ = map (print o format) result;\n" ^
Andreas@58039
   421
      "    val _ = print \"" ^ end_markerN ^ "\";\n" ^
Andreas@58039
   422
      "  in\n" ^
Andreas@58039
   423
      "    0\n" ^
Andreas@58039
   424
      "  end;\n" ^
Andreas@58039
   425
      "end;"
wenzelm@65901
   426
    val ml_source =
wenzelm@65901
   427
      "Control.MC.matchRedundantError := false; Control.MC.matchRedundantWarn := false;" ^
wenzelm@66783
   428
      "use " ^ ML_Syntax.print_string (Bash.string (File.platform_path code_path)) ^
wenzelm@66783
   429
      "; use " ^ ML_Syntax.print_string (Bash.string (File.platform_path driver_path)) ^
wenzelm@65901
   430
      "; Test.main ();"
wenzelm@65901
   431
    val cmd = "echo " ^ Bash.string ml_source ^ " | \"$ISABELLE_SMLNJ\""
Andreas@58039
   432
  in
Andreas@58039
   433
    {files = [(driver_path, driver)], compile_cmd = NONE, run_cmd = cmd, mk_code_file = K code_path}
Andreas@58039
   434
  end
Andreas@58039
   435
wenzelm@58832
   436
fun evaluate_in_smlnj ctxt =
wenzelm@64582
   437
  evaluate mk_driver_smlnj (SOME (ISABELLE_SMLNJ, "SMLNJ executable")) smlnjN ctxt
Andreas@58039
   438
wenzelm@64577
   439
wenzelm@64577
   440
(* driver for OCaml *)
Andreas@58039
   441
Andreas@58039
   442
val ocamlN = "OCaml"
Andreas@58039
   443
val ISABELLE_OCAMLC = "ISABELLE_OCAMLC"
Andreas@58039
   444
Andreas@58039
   445
fun mk_driver_ocaml _ path _ value_name =
Andreas@58039
   446
  let
Andreas@58039
   447
    val generatedN = "generated.ml"
Andreas@58039
   448
    val driverN = "driver.ml"
Andreas@58039
   449
Andreas@58039
   450
    val code_path = Path.append path (Path.basic generatedN)
Andreas@58039
   451
    val driver_path = Path.append path (Path.basic driverN)
wenzelm@64577
   452
    val driver =
Andreas@58039
   453
      "let format_term = function\n" ^
wenzelm@64577
   454
      "  | None -> \"\"\n" ^
Andreas@58039
   455
      "  | Some t -> t ();;\n" ^
Andreas@58039
   456
      "let format = function\n" ^
Andreas@58039
   457
      "  | (true, _) -> \"" ^ successN ^ "\\n\"\n" ^
Andreas@58039
   458
      "  | (false, x) -> \"" ^ failureN ^ "\" ^ format_term x ^ \"\\n\";;\n" ^
Andreas@58039
   459
      "let result = " ^ ("Generated." ^ value_name) ^ " ();;\n" ^
Andreas@58039
   460
      "let main x =\n" ^
Andreas@58039
   461
      "  let _ = print_string \"" ^ start_markerN ^ "\" in\n" ^
Andreas@58039
   462
      "  let _ = List.map (fun x -> print_string (format x)) result in\n" ^
Andreas@58039
   463
      "  print_string \"" ^ end_markerN ^ "\";;\n" ^
Andreas@58039
   464
      "main ();;"
Andreas@58039
   465
Andreas@58039
   466
    val compiled_path = Path.append path (Path.basic "test")
Andreas@58039
   467
    val compile_cmd =
wenzelm@65901
   468
      "\"$ISABELLE_OCAMLC\" -w pu -o " ^ File.bash_path compiled_path ^
wenzelm@65901
   469
      " -I " ^ File.bash_path path ^
wenzelm@65901
   470
      " nums.cma " ^ File.bash_path code_path ^ " " ^ File.bash_path driver_path
Andreas@58039
   471
wenzelm@62549
   472
    val run_cmd = File.bash_path compiled_path
Andreas@58039
   473
  in
Andreas@58039
   474
    {files = [(driver_path, driver)],
Andreas@58039
   475
     compile_cmd = SOME compile_cmd, run_cmd = run_cmd, mk_code_file = K code_path}
Andreas@58039
   476
  end
Andreas@58039
   477
wenzelm@58832
   478
fun evaluate_in_ocaml ctxt =
wenzelm@64582
   479
  evaluate mk_driver_ocaml (SOME (ISABELLE_OCAMLC, "ocamlc executable")) ocamlN ctxt
Andreas@58039
   480
wenzelm@64577
   481
wenzelm@64577
   482
(* driver for GHC *)
Andreas@58039
   483
Andreas@58039
   484
val ghcN = "GHC"
Andreas@58039
   485
val ISABELLE_GHC = "ISABELLE_GHC"
Andreas@58039
   486
Andreas@58039
   487
val ghc_options = Attrib.setup_config_string @{binding code_test_ghc} (K "")
Andreas@58039
   488
Andreas@58039
   489
fun mk_driver_ghc ctxt path modules value_name =
Andreas@58039
   490
  let
Andreas@58039
   491
    val driverN = "Main.hs"
Andreas@58039
   492
Andreas@58039
   493
    fun mk_code_file name = Path.append path (Path.basic (name ^ ".hs"))
Andreas@58039
   494
    val driver_path = Path.append path (Path.basic driverN)
wenzelm@64577
   495
    val driver =
Andreas@58039
   496
      "module Main where {\n" ^
Andreas@58039
   497
      String.concat (map (fn module => "import qualified " ^ module ^ ";\n") modules) ^
Andreas@58039
   498
      "main = do {\n" ^
Andreas@58039
   499
      "    let {\n" ^
wenzelm@64577
   500
      "      format_term Nothing = \"\";\n" ^
Andreas@58039
   501
      "      format_term (Just t) = t ();\n" ^
Andreas@58039
   502
      "      format (True, _) = \"" ^ successN ^ "\\n\";\n" ^
Andreas@58039
   503
      "      format (False, to) = \"" ^ failureN ^ "\" ++ format_term to ++ \"\\n\";\n" ^
Andreas@58039
   504
      "      result = " ^ value_name ^ " ();\n" ^
Andreas@58039
   505
      "    };\n" ^
Andreas@58039
   506
      "    Prelude.putStr \"" ^ start_markerN ^ "\";\n" ^
Andreas@58039
   507
      "    Prelude.mapM_ (putStr . format) result;\n" ^
Andreas@58039
   508
      "    Prelude.putStr \"" ^ end_markerN ^ "\";\n" ^
Andreas@58039
   509
      "  }\n" ^
Andreas@58039
   510
      "}\n"
Andreas@58039
   511
Andreas@58039
   512
    val compiled_path = Path.append path (Path.basic "test")
Andreas@58039
   513
    val compile_cmd =
wenzelm@65901
   514
      "\"$ISABELLE_GHC\" " ^ Code_Haskell.language_params ^ " " ^
wenzelm@65905
   515
      Config.get ctxt ghc_options ^ " -o " ^ Bash.string (File.platform_path compiled_path) ^ " " ^
wenzelm@65905
   516
      Bash.string (File.platform_path driver_path) ^ " -i" ^ Bash.string (File.platform_path path)
Andreas@58039
   517
wenzelm@62549
   518
    val run_cmd = File.bash_path compiled_path
Andreas@58039
   519
  in
Andreas@58039
   520
    {files = [(driver_path, driver)],
Andreas@58039
   521
     compile_cmd = SOME compile_cmd, run_cmd = run_cmd, mk_code_file = mk_code_file}
Andreas@58039
   522
  end
Andreas@58039
   523
wenzelm@58832
   524
fun evaluate_in_ghc ctxt =
wenzelm@64582
   525
  evaluate mk_driver_ghc (SOME (ISABELLE_GHC, "GHC executable")) ghcN ctxt
Andreas@58039
   526
wenzelm@64577
   527
wenzelm@64577
   528
(* driver for Scala *)
Andreas@58039
   529
Andreas@58039
   530
val scalaN = "Scala"
Andreas@58039
   531
Andreas@58039
   532
fun mk_driver_scala _ path _ value_name =
Andreas@58039
   533
  let
Andreas@58039
   534
    val generatedN = "Generated_Code"
Andreas@58039
   535
    val driverN = "Driver.scala"
Andreas@58039
   536
Andreas@58039
   537
    val code_path = Path.append path (Path.basic (generatedN ^ ".scala"))
Andreas@58039
   538
    val driver_path = Path.append path (Path.basic driverN)
wenzelm@64577
   539
    val driver =
Andreas@58039
   540
      "import " ^ generatedN ^ "._\n" ^
Andreas@58039
   541
      "object Test {\n" ^
Andreas@58039
   542
      "  def format_term(x : Option[Unit => String]) : String = x match {\n" ^
Andreas@58039
   543
      "    case None => \"\"\n" ^
Andreas@58039
   544
      "    case Some(x) => x(())\n" ^
Andreas@58039
   545
      "  }\n" ^
Andreas@58039
   546
      "  def format(term : (Boolean, Option[Unit => String])) : String = term match {\n" ^
Andreas@58039
   547
      "      case (true, _) => \"True\\n\"\n" ^
Andreas@58039
   548
      "      case (false, x) => \"False\" + format_term(x) + \"\\n\"\n" ^
Andreas@58039
   549
      "  }\n" ^
Andreas@58039
   550
      "  def main(args:Array[String]) = {\n" ^
Andreas@58039
   551
      "    val result = " ^ value_name ^ "(());\n" ^
Andreas@58039
   552
      "    print(\"" ^ start_markerN ^ "\");\n" ^
Andreas@58039
   553
      "    result.map{test:(Boolean, Option[Unit => String]) => print(format(test))};\n" ^
Andreas@58039
   554
      "    print(\"" ^ end_markerN ^ "\");\n" ^
Andreas@58039
   555
      "  }\n" ^
Andreas@58039
   556
      "}\n"
Andreas@58039
   557
Andreas@58039
   558
    val compile_cmd =
wenzelm@65900
   559
      "isabelle_scala scalac $ISABELLE_SCALAC_OPTIONS -d " ^ Bash.string (File.platform_path path) ^
wenzelm@65900
   560
      " -classpath " ^ Bash.string (File.platform_path path) ^ " " ^
wenzelm@65900
   561
      Bash.string (File.platform_path code_path) ^ " " ^
wenzelm@65900
   562
      Bash.string (File.platform_path driver_path)
Andreas@58039
   563
wenzelm@65900
   564
    val run_cmd = "isabelle_scala scala -cp " ^ Bash.string (File.platform_path path) ^ " Test"
Andreas@58039
   565
  in
Andreas@58039
   566
    {files = [(driver_path, driver)],
Andreas@58039
   567
     compile_cmd = SOME compile_cmd, run_cmd = run_cmd, mk_code_file = K code_path}
Andreas@58039
   568
  end
Andreas@58039
   569
wenzelm@64582
   570
fun evaluate_in_scala ctxt = evaluate mk_driver_scala NONE scalaN ctxt
Andreas@58039
   571
wenzelm@64580
   572
wenzelm@64580
   573
(* command setup *)
Andreas@58039
   574
wenzelm@64577
   575
val _ =
wenzelm@59936
   576
  Outer_Syntax.command @{command_keyword test_code}
Andreas@58039
   577
    "compile test cases to target languages, execute them and report results"
wenzelm@64580
   578
      (Scan.repeat1 Parse.prop -- (@{keyword "in"} |-- Scan.repeat1 Parse.name)
wenzelm@64580
   579
        >> (fn (ts, targets) => Toplevel.keep (test_code_cmd ts targets o Toplevel.context_of)))
Andreas@58039
   580
Andreas@66284
   581
val target_Scala = "Scala_eval"
Andreas@66284
   582
val target_Haskell = "Haskell_eval"
Andreas@66284
   583
Andreas@66284
   584
val _ = Theory.setup 
Andreas@66284
   585
   (Code_Target.add_derived_target (target_Scala, [(Code_Scala.target, I)])
Andreas@66284
   586
    #> Code_Target.add_derived_target (target_Haskell, [(Code_Haskell.target, I)]))
Andreas@66284
   587
wenzelm@64580
   588
val _ =
wenzelm@64580
   589
  Theory.setup (fold add_driver
wenzelm@64580
   590
    [(polymlN, (evaluate_in_polyml, Code_ML.target_SML)),
wenzelm@64580
   591
     (mltonN, (evaluate_in_mlton, Code_ML.target_SML)),
wenzelm@64580
   592
     (smlnjN, (evaluate_in_smlnj, Code_ML.target_SML)),
wenzelm@64580
   593
     (ocamlN, (evaluate_in_ocaml, Code_ML.target_OCaml)),
Andreas@66284
   594
     (ghcN, (evaluate_in_ghc, target_Haskell)),
Andreas@66284
   595
     (scalaN, (evaluate_in_scala, target_Scala))]
haftmann@67330
   596
  #> fold (fn target => Value_Command.add_evaluator (Binding.name target, eval_term target) #> snd)
wenzelm@64580
   597
      [polymlN, mltonN, smlnjN, ocamlN, ghcN, scalaN])
haftmann@59323
   598
Andreas@58039
   599
end