src/Pure/Tools/proof_general.ML
author wenzelm
Mon, 24 Jun 2013 17:03:53 +0200
changeset 53574 c88354589b43
parent 53154 bc0238c1f73a
child 53674 4b5941730bd8
permissions -rw-r--r--
more formal ProofGeneral command setup within theory Pure;
consider 'ProofGeneral.pr' as control command as well;
     1 (*  Title:      Pure/Tools/proof_general.ML
     2     Author:     David Aspinall
     3     Author:     Makarius
     4 
     5 Isabelle/Isar configuration for Proof General / Emacs.
     6 See also http://proofgeneral.inf.ed.ac.uk
     7 *)
     8 
     9 signature PROOF_GENERAL =
    10 sig
    11   type category = string
    12   val category_display: category
    13   val category_advanced_display: category
    14   val category_tracing: category
    15   val category_proof: category
    16   type pgiptype = string
    17   val pgipbool: pgiptype
    18   val pgipint: pgiptype
    19   val pgipfloat: pgiptype
    20   val pgipstring: pgiptype
    21   val preference: category -> string option ->
    22     (unit -> string) -> (string -> unit) -> string -> string -> string -> unit
    23   val preference_bool: category -> string option ->
    24     bool Unsynchronized.ref -> string -> string -> unit
    25   val preference_int: category -> string option ->
    26     int Unsynchronized.ref -> string -> string -> unit
    27   val preference_real: category -> string option ->
    28     real Unsynchronized.ref -> string -> string -> unit
    29   val preference_string: category -> string option ->
    30     string Unsynchronized.ref -> string -> string -> unit
    31   val preference_option: category -> string option -> string -> string -> string -> unit
    32   val process_pgip: string -> unit
    33   val tell_clear_goals: unit -> unit
    34   val tell_clear_response: unit -> unit
    35   val inform_file_processed: string -> unit
    36   val inform_file_retracted: string -> unit
    37   structure ThyLoad: sig val add_path: string -> unit end
    38   val thm_deps: bool Unsynchronized.ref
    39   val proof_generalN: string
    40   val init: unit -> unit
    41   val restart: unit -> unit
    42 end;
    43 
    44 structure ProofGeneral: PROOF_GENERAL =
    45 struct
    46 
    47 (** preferences **)
    48 
    49 (* type preference *)
    50 
    51 type category = string;
    52 val category_display = "Display";
    53 val category_advanced_display = "Advanced Display";
    54 val category_tracing = "Tracing";
    55 val category_proof = "Proof";
    56 
    57 type pgiptype = string;
    58 val pgipbool = "pgipbool";
    59 val pgipint = "pgipint";
    60 val pgipfloat = "pgipint";  (*NB: PG 3.7.x and 4.0 lack pgipfloat, but accept floats as pgipint*)
    61 val pgipstring = "pgipstring";
    62 
    63 type preference =
    64  {category: string,
    65   override: string option,
    66   descr: string,
    67   pgiptype: pgiptype,
    68   get: unit -> string,
    69   set: string -> unit};
    70 
    71 
    72 (* global preferences *)
    73 
    74 local
    75   val preferences =
    76     Synchronized.var "ProofGeneral.preferences" ([]: (string * preference) list);
    77 in
    78 
    79 fun add_preference name pref =
    80   Synchronized.change preferences (fn prefs =>
    81    (if not (AList.defined (op =) prefs name) then ()
    82     else warning ("Redefining ProofGeneral preference: " ^ quote name);
    83     AList.update (op =) (name, pref) prefs));
    84 
    85 fun set_preference name value =
    86   (case AList.lookup (op =) (Synchronized.value preferences) name of
    87     SOME {set, ...} => set value
    88   | NONE => error ("Unknown ProofGeneral preference: " ^ quote name));
    89 
    90 fun all_preferences () =
    91   rev (Synchronized.value preferences)
    92   |> map (fn (name, {category, descr, pgiptype, get, ...}) =>
    93     (category, {name = name, descr = descr, default = get (), pgiptype = pgiptype}))
    94   |> AList.group (op =);
    95 
    96 fun init_preferences () =
    97   Synchronized.value preferences
    98   |> List.app (fn (_, {set, override = SOME value, ...}) => set value | _ => ());
    99 
   100 end;
   101 
   102 
   103 
   104 (* raw preferences *)
   105 
   106 fun preference category override get set typ name descr =
   107   add_preference name
   108     {category = category, override = override, descr = descr, pgiptype = typ, get = get, set = set};
   109 
   110 fun preference_ref category override read write typ r =
   111   preference category override (fn () => read (! r)) (fn x => r := write x) typ;
   112 
   113 fun preference_bool x y = preference_ref x y Markup.print_bool Markup.parse_bool pgipbool;
   114 fun preference_int x y = preference_ref x y Markup.print_int Markup.parse_int pgipint;
   115 fun preference_real x y = preference_ref x y Markup.print_real Markup.parse_real pgipfloat;
   116 fun preference_string x y = preference_ref x y I I pgipstring;
   117 
   118 
   119 (* system options *)
   120 
   121 fun preference_option category override option_name pgip_name descr =
   122   let
   123     val typ = Options.default_typ option_name;
   124     val pgiptype =
   125       if typ = Options.boolT then pgipbool
   126       else if typ = Options.intT then pgipint
   127       else if typ = Options.realT then pgipfloat
   128       else pgipstring;
   129   in
   130     add_preference pgip_name
   131      {category = category,
   132       override = override,
   133       descr = descr,
   134       pgiptype = pgiptype,
   135       get = fn () => Options.get_default option_name,
   136       set = Options.put_default option_name}
   137   end;
   138 
   139 
   140 (* minimal PGIP support for <askprefs>, <haspref>, <setpref> *)
   141 
   142 local
   143 
   144 fun get_attr attrs name =
   145   (case Properties.get attrs name of
   146     SOME value => value
   147   | NONE => raise Fail ("Missing attribute: " ^ quote name));
   148 
   149 fun attr x y = [(x, y)] : XML.attributes;
   150 
   151 fun opt_attr _ NONE = []
   152   | opt_attr name (SOME value) = attr name value;
   153 
   154 val pgip_id = "dummy";
   155 val pgip_serial = Synchronized.counter ();
   156 
   157 fun output_pgip refid refseq content =
   158   XML.Elem (("pgip",
   159     attr "tag" "Isabelle/Isar" @
   160     attr "id" pgip_id @
   161     opt_attr "destid" refid @
   162     attr "class" "pg" @
   163     opt_attr "refid" refid @
   164     attr "refseq" refseq @
   165     attr "seq" (string_of_int (pgip_serial ()))), content)
   166   |> XML.string_of
   167   |> Output.urgent_message;
   168 
   169 
   170 fun invalid_pgip () = raise Fail "Invalid PGIP packet";
   171 
   172 fun haspref {name, descr, default, pgiptype} =
   173   XML.Elem (("haspref", [("name", name), ("descr", descr), ("default", default)]),
   174     [XML.Elem ((pgiptype, []), [])]);
   175 
   176 fun process_element refid refseq (XML.Elem (("askprefs", _), _)) =
   177       all_preferences () |> List.app (fn (category, prefs) =>
   178         output_pgip refid refseq
   179           [XML.Elem (("hasprefs", [("prefcategory", category)]), map haspref prefs)])
   180   | process_element _ _ (XML.Elem (("setpref", attrs), data)) =
   181       let
   182         val name =
   183           (case Properties.get attrs "name" of
   184             SOME name => name
   185           | NONE => invalid_pgip ());
   186         val value = XML.content_of data;
   187       in set_preference name value end
   188   | process_element _ _ _ = invalid_pgip ();
   189 
   190 in
   191 
   192 fun process_pgip str =
   193   (case XML.parse str of
   194     XML.Elem (("pgip", attrs), pgips) =>
   195       let
   196         val class = get_attr attrs "class";
   197         val dest = Properties.get attrs "destid";
   198         val refid = Properties.get attrs "id";
   199         val refseq = get_attr attrs "seq";
   200         val processit =
   201           (case dest of
   202             NONE => class = "pa"
   203           | SOME id => id = pgip_id);
   204        in if processit then List.app (process_element refid refseq) pgips else () end
   205   | _ => invalid_pgip ())
   206   handle Fail msg => raise Fail (msg ^ "\n" ^ str);
   207 
   208 end;
   209 
   210 
   211 (** messages **)
   212 
   213 (* render markup *)
   214 
   215 fun special ch = chr 1 ^ ch;
   216 
   217 local
   218 
   219 fun render_trees ts = fold render_tree ts
   220 and render_tree t =
   221   (case XML.unwrap_elem t of
   222     SOME (_, ts) => render_trees ts
   223   | NONE =>
   224       (case t of
   225         XML.Text s => Buffer.add s
   226       | XML.Elem ((name, props), ts) =>
   227           let
   228             val (bg, en) =
   229               if null ts then Markup.no_output
   230               else if name = Markup.stateN then (special "O" ^ "\n", "\n" ^ special "P")
   231               else if name = Markup.sendbackN then (special "W", special "X")
   232               else if name = Markup.intensifyN then (special "0", special "1")
   233               else if name = Markup.tfreeN then (special "C", special "A")
   234               else if name = Markup.tvarN then (special "D", special "A")
   235               else if name = Markup.freeN then (special "E", special "A")
   236               else if name = Markup.boundN then (special "F", special "A")
   237               else if name = Markup.varN then (special "G", special "A")
   238               else if name = Markup.skolemN then (special "H", special "A")
   239               else
   240                 (case Markup.get_entity_kind (name, props) of
   241                   SOME kind =>
   242                     if kind = Markup.classN then (special "B", special "A")
   243                     else Markup.no_output
   244                 | NONE => Markup.no_output);
   245           in Buffer.add bg #> render_trees ts #> Buffer.add en end));
   246 
   247 in
   248 
   249 fun render text =
   250   Buffer.content (render_trees (YXML.parse_body text) Buffer.empty);
   251 
   252 end;
   253 
   254 
   255 (* hooks *)
   256 
   257 fun message bg en prfx text =
   258   (case render text of
   259     "" => ()
   260   | s => Output.physical_writeln (enclose bg en (prefix_lines prfx s)));
   261 
   262 fun setup_messages () =
   263  (Output.Private_Hooks.writeln_fn := message "" "" "";
   264   Output.Private_Hooks.status_fn := (fn _ => ());
   265   Output.Private_Hooks.report_fn := (fn _ => ());
   266   Output.Private_Hooks.urgent_message_fn := message (special "I") (special "J") "";
   267   Output.Private_Hooks.tracing_fn := message (special "I" ^ special "V") (special "J") "";
   268   Output.Private_Hooks.warning_fn := message (special "K") (special "L") "### ";
   269   Output.Private_Hooks.error_fn := (fn (_, s) => message (special "M") (special "N") "*** " s);
   270   Output.Private_Hooks.prompt_fn := (fn s => Output.physical_stdout (render s ^ special "S")));
   271 
   272 
   273 (* notification *)
   274 
   275 val emacs_notify = message (special "I") (special "J") "";
   276 
   277 fun tell_clear_goals () =
   278   emacs_notify "Proof General, please clear the goals buffer.";
   279 
   280 fun tell_clear_response () =
   281   emacs_notify "Proof General, please clear the response buffer.";
   282 
   283 fun tell_file_loaded path =
   284   emacs_notify ("Proof General, this file is loaded: " ^ quote (File.platform_path path));
   285 
   286 fun tell_file_retracted path =
   287   emacs_notify ("Proof General, you can unlock the file " ^ quote (File.platform_path path));
   288 
   289 
   290 
   291 (** theory loader **)
   292 
   293 (* fake old ThyLoad -- with new semantics *)
   294 
   295 structure ThyLoad =
   296 struct
   297   val add_path = Thy_Load.set_master_path o Path.explode;
   298 end;
   299 
   300 
   301 (* actions *)
   302 
   303 local
   304 
   305 fun trace_action action name =
   306   if action = Thy_Info.Update then
   307     List.app tell_file_loaded (Thy_Info.loaded_files name)
   308   else if action = Thy_Info.Remove then
   309     List.app tell_file_retracted (Thy_Info.loaded_files name)
   310   else ();
   311 
   312 in
   313   fun setup_thy_loader () = Thy_Info.add_hook trace_action;
   314   fun sync_thy_loader () = List.app (trace_action Thy_Info.Update) (Thy_Info.get_names ());
   315 end;
   316 
   317 
   318 (* get informed about files *)
   319 
   320 (*liberal low-level version*)
   321 val thy_name = perhaps (try (unsuffix ".thy")) o List.last o space_explode "/";
   322 
   323 val inform_file_retracted = Thy_Info.kill_thy o thy_name;
   324 
   325 fun inform_file_processed file =
   326   let
   327     val name = thy_name file;
   328     val _ = name = "" andalso error ("Bad file name: " ^ quote file);
   329     val _ =
   330       Thy_Info.register_thy (Toplevel.end_theory Position.none (Isar.state ()))
   331         handle ERROR msg =>
   332           (warning (cat_lines ["Failed to register theory: " ^ quote name, msg]);
   333             tell_file_retracted (Thy_Load.thy_path (Path.basic name)))
   334     val _ = Isar.init ();
   335   in () end;
   336 
   337 
   338 
   339 (** theorem dependencies **)
   340 
   341 (* thm_deps *)
   342 
   343 local
   344 
   345 fun add_proof_body (PBody {thms, ...}) =
   346   thms |> fold (fn (_, (name, _, _)) => name <> "" ? Symtab.update (name, ()));
   347 
   348 fun add_thm th =
   349   (case Thm.proof_body_of th of
   350     PBody {proof = PThm (_, ((name, _, _), body)), ...} =>
   351       if Thm.has_name_hint th andalso Thm.get_name_hint th = name
   352       then add_proof_body (Future.join body)
   353       else I
   354   | body => add_proof_body body);
   355 
   356 in
   357 
   358 fun get_thm_deps ths =
   359   let
   360     (* FIXME proper derivation names!? *)
   361     val names = map Thm.get_name_hint (filter Thm.has_name_hint ths);
   362     val deps = Symtab.keys (fold add_thm ths Symtab.empty);
   363   in (names, deps) end;
   364 
   365 end;
   366 
   367 
   368 (* report via hook *)
   369 
   370 val thm_deps = Unsynchronized.ref false;
   371 
   372 local
   373 
   374 val spaces_quote = space_implode " " o map quote;
   375 
   376 fun thm_deps_message (thms, deps) =
   377   emacs_notify ("Proof General, theorem dependencies of " ^ thms ^ " are " ^ deps);
   378 
   379 in
   380 
   381 fun setup_present_hook () = Toplevel.add_hook (fn _ => fn state => fn state' =>
   382   if ! thm_deps andalso can Toplevel.theory_of state andalso Toplevel.is_theory state'
   383   then
   384     let
   385       val prev_facts = Global_Theory.facts_of (Toplevel.theory_of state);
   386       val facts = Global_Theory.facts_of (Toplevel.theory_of state');
   387       val (names, deps) = get_thm_deps (maps #2 (Facts.dest_static [prev_facts] facts));
   388     in
   389       if null names orelse null deps then ()
   390       else thm_deps_message (spaces_quote names, spaces_quote deps)
   391     end
   392   else ());
   393 
   394 end;
   395 
   396 
   397 
   398 (** startup **)
   399 
   400 (* init *)
   401 
   402 val proof_generalN = "ProofGeneral";
   403 
   404 val initialized = Unsynchronized.ref false;
   405 
   406 fun init () =
   407   (if ! initialized then ()
   408    else
   409     (Output.add_mode Symbol.xsymbolsN Symbol.output Output.default_escape;
   410      Output.add_mode proof_generalN Output.default_output Output.default_escape;
   411      Markup.add_mode proof_generalN YXML.output_markup;
   412      setup_messages ();
   413      setup_thy_loader ();
   414      setup_present_hook ();
   415      initialized := true);
   416    init_preferences ();
   417    sync_thy_loader ();
   418    Unsynchronized.change print_mode (update (op =) proof_generalN);
   419    Secure.PG_setup ();
   420    Isar.toplevel_loop TextIO.stdIn
   421     {init = true, welcome = true, sync = true, secure = Secure.is_secure ()});
   422 
   423 
   424 (* restart *)
   425 
   426 val welcome = Output.urgent_message o Session.welcome;
   427 
   428 fun restart () =
   429  (sync_thy_loader ();
   430   tell_clear_goals ();
   431   tell_clear_response ();
   432   Isar.init ();
   433   welcome ());
   434 
   435 end;
   436