src/Pure/ProofGeneral/proof_general_pgip.ML
author wenzelm
Sun Jan 06 15:57:54 2008 +0100 (2008-01-06)
changeset 25847 713519ba6860
parent 25445 01f3686f4304
child 26109 c69c3559355b
permissions -rw-r--r--
removed obsolete prompt markup;
     1 (*  Title:      Pure/ProofGeneral/proof_general_pgip.ML
     2     ID:         $Id$
     3     Author:     David Aspinall and Markus Wenzel
     4 
     5 Isabelle configuration for Proof General using PGIP protocol.
     6 See http://proofgeneral.inf.ed.ac.uk/kit
     7 *)
     8 
     9 signature PROOF_GENERAL_PGIP =
    10 sig
    11   val init_pgip: bool -> unit             (* main PGIP loop with true; fail with false *)
    12 
    13   (* These two are just to support the semi-PGIP Emacs mode *)
    14   val init_pgip_channel: (string -> unit) -> unit
    15   val process_pgip: string -> unit
    16 
    17   (* More message functions... *)
    18   val nonfatal_error : string -> unit     (* recoverable (batch) error: carry on scripting *)
    19   val log_msg : string -> unit            (* for internal log messages *)
    20   val error_with_pos : PgipTypes.displayarea -> PgipTypes.fatality -> Position.T -> string -> unit
    21 
    22   val get_currently_open_file : unit -> Path.T option  (* interface focus *)
    23 end
    24 
    25 structure ProofGeneralPgip : PROOF_GENERAL_PGIP  =
    26 struct
    27 
    28 open Pgip;
    29 
    30 
    31 (** print mode **)
    32 
    33 val proof_generalN = "ProofGeneral";
    34 val pgmlsymbols_flag = ref true;
    35 
    36 
    37 (* symbol output *)
    38 
    39 local
    40 
    41 fun xsym_output "\\" = "\\\\"
    42   | xsym_output s = if Symbol.is_raw s then Symbol.decode_raw s else s;
    43 
    44 fun pgml_sym s =
    45   (case Symbol.decode s of
    46     Symbol.Char s => XML.text s
    47   | Symbol.Sym sn => 
    48     let val ascii = implode (map xsym_output (Symbol.explode s))
    49     in if !pgmlsymbols_flag then XML.element "sym" [("name", sn)] [XML.text ascii]
    50        else  ascii end
    51   | Symbol.Ctrl sn => XML.element "ctrl" [("name", sn)] [XML.text sn] (* FIXME: no such PGML *)
    52   | Symbol.Raw raw => raw);
    53 
    54 fun pgml_output str =
    55   let val syms = Symbol.explode str
    56   in (implode (map pgml_sym syms), Symbol.length syms) end;
    57 
    58 in
    59 
    60 fun setup_proofgeneral_output () =
    61   Output.add_mode proof_generalN pgml_output Symbol.encode_raw;
    62 
    63 end;
    64 
    65 
    66 (* token translations *)
    67 
    68 local
    69 
    70 val class_tag = "class"
    71 val tfree_tag = "tfree"
    72 val tvar_tag = "tvar"
    73 val free_tag = "free"
    74 val bound_tag = "bound"
    75 val var_tag = "var"
    76 val skolem_tag = "skolem"
    77 
    78 fun xml_atom kind x = XML.element "atom" [("kind", kind)] [XML.text x];
    79 
    80 fun tagit kind x =
    81   (xml_atom kind x, Symbol.length (Symbol.explode x));
    82 
    83 fun free_or_skolem x =
    84   (case try Name.dest_skolem x of
    85     NONE => tagit free_tag x
    86   | SOME x' => tagit skolem_tag x');
    87 
    88 fun var_or_skolem s =
    89   (case Lexicon.read_variable s of
    90     SOME (x, i) =>
    91       (case try Name.dest_skolem x of
    92         NONE => tagit var_tag s
    93       | SOME x' => tagit skolem_tag
    94           (setmp show_question_marks true Term.string_of_vname (x', i)))
    95   | NONE => tagit var_tag s);
    96 
    97 val proof_general_trans =
    98  Syntax.tokentrans_mode proof_generalN
    99   [("class", tagit class_tag),
   100    ("tfree", tagit tfree_tag),
   101    ("tvar", tagit tvar_tag),
   102    ("free", free_or_skolem),
   103    ("bound", tagit bound_tag),
   104    ("var", var_or_skolem)];
   105 
   106 in
   107 
   108 val _ = Context.add_setup (Sign.add_tokentrfuns proof_general_trans);
   109 
   110 end;
   111 
   112 
   113 (* assembling and issuing PGIP packets *)
   114 
   115 val pgip_refid  = ref NONE: string option ref;
   116 val pgip_refseq = ref NONE: int option ref;
   117 
   118 local
   119   val pgip_class  = "pg"
   120   val pgip_tag = "Isabelle/Isar"
   121   val pgip_id = ref ""
   122   val pgip_seq = ref 0
   123   fun pgip_serial () = inc pgip_seq
   124 
   125   fun assemble_pgips pgips =
   126     Pgip { tag = SOME pgip_tag,
   127            class = pgip_class,
   128            seq = pgip_serial(),
   129            id = !pgip_id,
   130            destid = !pgip_refid,
   131            (* destid=refid since Isabelle only communicates back to sender *)
   132            refid  = !pgip_refid,
   133            refseq = !pgip_refseq,
   134            content = pgips }
   135 in
   136 
   137 fun init_pgip_session_id () =
   138     pgip_id := getenv "HOSTNAME" ^ "/" ^ getenv "USER" ^ "/" ^
   139                getenv "ISABELLE_PID" ^ "/" ^ Time.toString (Time.now ())
   140 
   141 fun matching_pgip_id id = (id = !pgip_id)
   142 
   143 val output_xml_fn = ref Output.writeln_default
   144 fun output_xml s = (!output_xml_fn) (XML.string_of_tree s);  (* TODO: string buffer *)
   145 
   146 val output_pgips =
   147   XML.string_of_tree o PgipOutput.output o assemble_pgips o map PgipOutput.output;
   148 
   149 val output_pgmlterm = 
   150   XML.string_of_tree o Pgml.pgmlterm_to_xml;
   151 
   152 val output_pgmltext = 
   153   XML.string_of_tree o Pgml.pgml_to_xml;
   154 
   155 
   156 fun issue_pgip_rawtext str =
   157     output_xml (PgipOutput.output (assemble_pgips [XML.Output str]))
   158 
   159 fun issue_pgips pgipops =
   160     output_xml (PgipOutput.output (assemble_pgips (map PgipOutput.output pgipops)));
   161 
   162 fun issue_pgip pgipop =
   163     output_xml (PgipOutput.output (assemble_pgips [PgipOutput.output pgipop]));
   164 
   165 end;
   166 
   167 
   168 fun pgml area terms = Pgml.Pgml { version=NONE,systemid=NONE,
   169                                   area=SOME area, content=terms }
   170 
   171 (** messages and notification **)
   172 
   173 local
   174     val delay_msgs = ref false   (*true: accumulate messages*)
   175     val delayed_msgs = ref []
   176 
   177     fun queue_or_issue pgip =
   178         if ! delay_msgs then
   179             delayed_msgs := pgip :: ! delayed_msgs
   180         else issue_pgip pgip
   181 
   182     fun wrap_pgml area s = 
   183         if String.isPrefix "<pgml" s then  
   184             XML.Output s  (* already pgml outermost *)
   185         else 
   186             Pgml.pgml_to_xml (pgml area [Pgml.Raw (XML.Output s)]) (* mixed *)
   187 
   188 in
   189     fun normalmsg area s =
   190         let
   191             val content = wrap_pgml area s
   192             val pgip = Normalresponse { content=[content] }
   193         in
   194             queue_or_issue pgip
   195         end
   196 
   197     fun errormsg area fatality s =
   198         let
   199             val content = wrap_pgml area s
   200             val pgip = Errorresponse { fatality=fatality,
   201                                        location=NONE,
   202                                        content=[content] }
   203         in
   204             queue_or_issue pgip
   205         end
   206 
   207     (* Error responses with useful locations *)
   208     fun error_with_pos area fatality pos s =
   209         let
   210               val content = wrap_pgml area s
   211               val pgip = Errorresponse { fatality=fatality,
   212                                          location=SOME (PgipIsabelle.location_of_position pos),
   213                                          content=[content] }
   214         in
   215             queue_or_issue pgip
   216         end
   217 
   218     fun start_delay_msgs () = (set delay_msgs; delayed_msgs := [])
   219     fun end_delayed_msgs () = (reset delay_msgs; ! delayed_msgs)
   220 end;
   221 
   222 (* NB: all of the standard error/message functions now expect already-escaped text.
   223    FIXME: this may cause us problems now we're generating trees; on the other
   224    hand the output functions were tuned some time ago, so it ought to be
   225    enough to use XML.Output always above. *)
   226 (* NB 2: all of standard functions print strings terminated with new lines, but we don't
   227    add new lines explicitly in PGIP: they are left implicit.  It means that PGIP messages
   228    can't be written without newlines. *)
   229 
   230 fun setup_messages () =
   231  (Output.writeln_fn := (fn s => normalmsg Message s);
   232   Output.priority_fn := (fn s => normalmsg Status s);
   233   Output.tracing_fn := (fn s => normalmsg  Tracing s);
   234   Output.warning_fn := (fn s => errormsg Message Warning s);
   235   Output.error_fn := (fn s => errormsg Message Fatal s);
   236   Output.debug_fn := (fn s => errormsg Message Debug s));
   237 
   238 fun panic s = (errormsg Message Panic ("## SYSTEM EXIT ##\n" ^ s); exit 1);
   239 fun nonfatal_error s = errormsg Message Nonfatal s;
   240 fun log_msg s = errormsg Message Log s;
   241 
   242 
   243 (* immediate messages *)
   244 
   245 fun tell_clear_goals () = 
   246     issue_pgip (Normalresponse { content = [Pgml.pgml_to_xml (pgml Display [])] })
   247 fun tell_clear_response () = 
   248     issue_pgip (Normalresponse { content = [Pgml.pgml_to_xml (pgml Message [])] })
   249 
   250 fun tell_file_loaded completed path   =
   251     issue_pgip (Informfileloaded {url=PgipTypes.pgipurl_of_path path,
   252                                   completed=completed})
   253 fun tell_file_outdated completed path   =
   254     issue_pgip (Informfileoutdated {url=PgipTypes.pgipurl_of_path path,
   255                                     completed=completed})
   256 fun tell_file_retracted completed path =
   257     issue_pgip (Informfileretracted {url=PgipTypes.pgipurl_of_path path,
   258                                      completed=completed})
   259 
   260 
   261 (* common markup *)
   262 
   263 local
   264 
   265 val no_text = chr 0;
   266 
   267 val pgmlterms_no_text= [Pgml.Raw (XML.Output no_text)]
   268 
   269 fun split_markup text =
   270   (case space_explode no_text text of
   271     [bg, en] => (bg, en)
   272   | _ => (error ("Internal error: failed to split XML markup:\n" ^ text); ("", "")));
   273 
   274 
   275 fun block_markup markup =
   276     let 
   277       val pgml = Pgml.Box { orient = NONE, 
   278                             indent = Markup.get_int markup Markup.indentN,
   279                             content = pgmlterms_no_text }
   280     in split_markup (output_pgmlterm pgml) end;
   281 
   282 fun break_markup markup =
   283     let 
   284       val pgml = Pgml.Break { mandatory = NONE,
   285                               indent = Markup.get_int markup Markup.widthN }
   286     in (output_pgmlterm pgml, "") end;
   287 
   288 fun fbreak_markup markup =
   289     let 
   290       val pgml = Pgml.Break { mandatory = SOME true, indent = NONE }
   291     in (output_pgmlterm pgml, "") end;
   292 
   293 val state_markup =
   294     split_markup (output_pgmltext (pgml PgipTypes.Display pgmlterms_no_text))
   295 
   296 fun proof_general_markup (markup as (name, _)) =
   297         if name = Markup.blockN    then block_markup markup
   298    else if name = Markup.breakN    then break_markup markup
   299    else if name = Markup.fbreakN   then fbreak_markup markup
   300 (* else if name = Markup.classN    then class_markup markup
   301    else if name = Markup.tyconN    then tycon_markup markup
   302    else if name = Markup.constN    then const_markup markup
   303    else if name = Markup.axiomN    then axiom_markup markup
   304    else if name = Markup.sortN     then sort_markup markup
   305    else if name = Markup.typN      then typ_markup markup
   306    else if name = Markup.termN     then term_markup markup
   307    else if name = Markup.keywordN  then keyword_markup markup
   308    else if name = Markup.commandN  then command_markup markup*)
   309    else if name = Markup.stateN    then state_markup
   310 (* else if name = Markup.subgoalN  then subgoal_markup () *)
   311    else ("", "");
   312 
   313 in
   314 
   315 val _ = Markup.add_mode proof_generalN proof_general_markup;
   316 
   317 end;
   318 
   319 
   320 (* theory loader actions *)
   321 
   322 local
   323   (* da: TODO: PGIP has a completed flag so the prover can indicate to the
   324      interface which files are busy performing a particular action.
   325      To make use of this we need to adjust the hook in thy_info.ML
   326      (may actually be difficult to tell the interface *which* action is in
   327       progress, but we could add a generic "Lock" action which uses
   328       informfileloaded: the broker/UI should not infer too much from incomplete
   329       operations).
   330    *)
   331 fun trace_action action name =
   332   if action = ThyInfo.Update then
   333     List.app (tell_file_loaded true) (ThyInfo.loaded_files name)
   334   else if action = ThyInfo.Outdate then
   335     List.app (tell_file_outdated true) (ThyInfo.loaded_files name)
   336   else if action = ThyInfo.Remove then
   337       List.app (tell_file_retracted true) (ThyInfo.loaded_files name)
   338   else ()
   339 
   340 
   341 in
   342   fun setup_thy_loader () = ThyInfo.add_hook trace_action;
   343   fun sync_thy_loader () = List.app (trace_action ThyInfo.Update) (ThyInfo.names ());
   344 end;
   345 
   346 
   347 (* get informed about files *)
   348 
   349 val thy_name = Path.implode o #1 o Path.split_ext o Path.base;
   350 
   351 val inform_file_retracted = ThyInfo.if_known_thy ThyInfo.remove_thy o thy_name;
   352 val inform_file_processed = ThyInfo.if_known_thy ThyInfo.touch_child_thys o thy_name;
   353 
   354 fun proper_inform_file_processed path state =
   355   let val name = thy_name path in
   356     if Toplevel.is_toplevel state andalso ThyInfo.known_thy name then
   357      (ThyInfo.touch_child_thys name;
   358       ThyInfo.register_thy name handle ERROR msg =>
   359        (warning (cat_lines [msg, "Failed to register theory: " ^ quote name]);
   360         tell_file_retracted true (Path.base path)))
   361     else raise Toplevel.UNDEF
   362   end;
   363 
   364 
   365 (* restart top-level loop (keeps most state information) *)
   366 
   367 val welcome = priority o Session.welcome;
   368 
   369 fun restart () =
   370     (sync_thy_loader ();
   371      tell_clear_goals ();
   372      tell_clear_response ();
   373      welcome ();
   374      raise Toplevel.RESTART)
   375 
   376 
   377 (* theorem dependency output *)
   378 
   379 val show_theorem_dependencies = ref false;
   380 
   381 local
   382 
   383 val spaces_quote = space_implode " " o map quote;
   384 
   385 fun thm_deps_message (thms, deps) =
   386     let
   387         val valuethms = XML.Elem("value",[("name", "thms")],[XML.Text thms])
   388         val valuedeps = XML.Elem("value",[("name", "deps")],[XML.Text deps])
   389     in
   390         issue_pgip (Metainforesponse {attrs=[("infotype", "isabelle_theorem_dependencies")],
   391                                       content=[valuethms,valuedeps]})
   392     end
   393 
   394 fun tell_thm_deps ths =
   395   if !show_theorem_dependencies then
   396       let
   397         val names = map PureThy.get_name_hint (filter PureThy.has_name_hint ths);
   398         val deps = (Symtab.keys (fold Proofterm.thms_of_proof'
   399                                         (map Thm.proof_of ths) Symtab.empty))
   400       in
   401           if null names orelse null deps then ()
   402           else thm_deps_message (spaces_quote names, spaces_quote deps)
   403       end
   404   else ()
   405 
   406 in
   407 
   408 fun setup_present_hook () =
   409   Present.add_hook (fn _ => fn res => tell_thm_deps (maps #2 res));
   410 
   411 end;
   412 
   413 (** lexicalstructure element with keywords (PGIP version of elisp keywords file) **)
   414 
   415 fun lexicalstructure_keywords () =
   416     let val commands = OuterSyntax.dest_keywords ()
   417         fun category_of k = if k mem commands then "major" else "minor"
   418          (* NB: we might filter to only include words like elisp case (OuterSyntax.is_keyword). *)
   419         fun keyword_elt (keyword,help,kind,_) =
   420             XML.Elem("keyword", [("word", keyword), ("category", category_of kind)],
   421                      [XML.Elem("shorthelp", [], [XML.Text help])])
   422         in
   423             (* Also, note we don't call init_outer_syntax here to add interface commands,
   424             but they should never appear in scripts anyway so it shouldn't matter *)
   425             Lexicalstructure {content = map keyword_elt (OuterSyntax.dest_parsers()) }
   426         end
   427 
   428 (* TODO: we can issue a lexicalstructure/keyword when the syntax gets extended dynamically;
   429    hooks needed in outer_syntax.ML to do that. *)
   430 
   431 
   432 (* Configuration: GUI config, proverinfo messages *)
   433 
   434 local
   435     val isabellewww = "http://isabelle.in.tum.de/"
   436     val staticconfig = "~~/lib/ProofGeneral/pgip_isar.xml"
   437     fun orenv v d = case getenv v of "" => d  | s => s
   438     fun config_file()  = orenv "ISABELLE_PGIPCONFIG" staticconfig
   439     fun isabelle_www() = orenv "ISABELLE_HOMEPAGE" isabellewww
   440 in
   441 fun send_pgip_config () =
   442     let
   443         val path = Path.explode (config_file())
   444         val ex = File.exists path
   445 
   446         val wwwpage =
   447             (Url.explode (isabelle_www()))
   448             handle ERROR _ =>
   449                    (panic ("Error in URL in environment variable ISABELLE_HOMEPAGE.");
   450                         Url.explode isabellewww)
   451 
   452         val proverinfo =
   453             Proverinfo { name = "Isabelle",
   454                          version = version,
   455                          instance = Session.name(),
   456                          descr = "The Isabelle/Isar theorem prover",
   457                          url = wwwpage,
   458                          filenameextns = ".thy;" }
   459     in
   460         if ex then
   461             (issue_pgip proverinfo;
   462              issue_pgip_rawtext (File.read path);
   463              issue_pgip (lexicalstructure_keywords()))
   464         else panic ("PGIP configuration file \"" ^ config_file() ^ "\" not found")
   465     end;
   466 end
   467 
   468 
   469 (* Preferences: tweak for PGIP interfaces *)
   470 
   471 val preferences = ref Preferences.preferences;
   472 
   473 fun setup_preferences_tweak() =
   474     preferences :=
   475      (!preferences |> Preferences.set_default ("show-question-marks","false")
   476                    |> Preferences.remove "show-question-marks"    (* we use markup, not ?s *)
   477                    |> Preferences.remove "theorem-dependencies"   (* set internally *)
   478                    |> Preferences.remove "full-proofs")           (* set internally *)
   479 
   480 
   481 
   482 (* Sending commands to Isar *)
   483 
   484 fun isarcmd s =
   485     s |> OuterSyntax.scan |> OuterSyntax.read
   486       (*|> map (Toplevel.position Position.none o #3)*)
   487       |> map #3
   488       |> Toplevel.>>>;
   489 
   490 (* TODO:
   491     - apply a command given a transition function, e.g. IsarCmd.undo.
   492     - fix position from path of currently open file [line numbers risk garbling though].
   493 *)
   494 
   495 (* load an arbitrary file (must be .thy or .ML) *)
   496 
   497 fun use_thy_or_ml_file file =
   498     let
   499         val (path,extn) = Path.split_ext (Path.explode file)
   500     in
   501         case extn of
   502             "" => isarcmd ("use_thy " ^ quote (Path.implode path))
   503           | "thy" => isarcmd ("use_thy " ^ quote (Path.implode path))
   504           | "ML" => isarcmd ("use " ^ quote file)
   505           | other => error ("Don't know how to read a file with extension " ^ quote other)
   506     end
   507 
   508 
   509 (******* PGIP actions *******)
   510 
   511 
   512 (* Responses to each of the PGIP input commands.
   513    These are programmed uniformly for extensibility. *)
   514 
   515 fun askpgip (Askpgip _) =
   516     (issue_pgip
   517          (Usespgip { version = PgipIsabelle.isabelle_pgip_version_supported,
   518                      pgipelems = PgipIsabelle.accepted_inputs });
   519      send_pgip_config())
   520 
   521 fun askpgml (Askpgml _) =
   522     issue_pgip
   523         (Usespgml { version = PgipIsabelle.isabelle_pgml_version_supported })
   524 
   525 fun askprefs (Askprefs _) =
   526     let
   527         fun preference_of {name, descr, default, pgiptype, get, set } =
   528             { name = name, descr = SOME descr, default = SOME default,
   529               pgiptype = pgiptype }
   530     in
   531         List.app (fn (prefcat, prefs) =>
   532                      issue_pgip (Hasprefs {prefcategory=SOME prefcat,
   533                                            prefs=map preference_of prefs}))
   534                  (!preferences)
   535     end
   536 
   537 fun askconfig (Askconfig _) = () (* TODO: add config response *)
   538 
   539 local
   540     fun lookuppref pref =
   541         case AList.lookup (op =)
   542                           (map (fn p => (#name p,p))
   543                                (maps snd (!preferences))) pref of
   544             NONE => error ("Unknown prover preference: " ^ quote pref)
   545           | SOME p => p
   546 in
   547 fun setpref (Setpref vs) =
   548     let
   549         val name = #name vs
   550         val value = #value vs
   551         val set = #set (lookuppref name)
   552     in
   553         set value
   554     end
   555 
   556 fun getpref (Getpref vs) =
   557     let
   558         val name = #name vs
   559         val get = #get (lookuppref name)
   560     in
   561         issue_pgip (Prefval {name=name, value=get ()})
   562     end
   563 end
   564 
   565 fun proverinit _ = restart ()
   566 
   567 fun proverexit _ = isarcmd "quit"
   568 
   569 fun set_proverflag_quiet b = 
   570     isarcmd (if b then "disable_pr" else "enable_pr")
   571 
   572 fun set_proverflag_pgmlsymbols b =
   573     (pgmlsymbols_flag := b;
   574       NAMED_CRITICAL "print_mode" (fn () =>
   575         change print_mode 
   576             (fn mode =>
   577                 remove (op =) Symbol.xsymbolsN mode @ (if b then [Symbol.xsymbolsN] else []))))
   578 
   579 fun set_proverflag_thmdeps b =
   580     (show_theorem_dependencies := b;
   581      Proofterm.proofs := (if b then 1 else 2))
   582 
   583 fun setproverflag (Setproverflag vs) =
   584     let 
   585         val flagname = #flagname vs
   586         val value = #value vs
   587     in
   588         (case flagname of
   589              "quiet"            => set_proverflag_quiet value
   590            | "pgmlsymbols"      => set_proverflag_pgmlsymbols value
   591            | "metainfo:thmdeps" => set_proverflag_thmdeps value 
   592            | _ => log_msg ("Unrecognised prover control flag: " ^ 
   593                            (quote flagname) ^ " ignored."))
   594     end 
   595 
   596 
   597 fun dostep (Dostep vs) =
   598     let
   599         val text = #text vs
   600     in
   601         isarcmd text
   602     end
   603 
   604 fun undostep (Undostep vs) =
   605     let
   606         val times = #times vs
   607     in
   608         isarcmd ("undos_proof " ^ Int.toString times)
   609     end
   610 
   611 fun redostep _ = isarcmd "redo"
   612 
   613 fun abortgoal _ = isarcmd "kill" (* was: ProofGeneral.kill_proof *)
   614 
   615 
   616 (*** PGIP identifier tables ***)
   617 
   618 (* TODO: these ones should be triggered by hooks after a
   619    declaration addition/removal, to be sent automatically. *)
   620 
   621 fun addids t  = issue_pgip (Addids {idtables = [t]})
   622 fun delids t  = issue_pgip (Delids {idtables = [t]})
   623 
   624 fun askids (Askids vs) =
   625     let
   626         val url = #url vs            (* ask for identifiers within a file *)
   627         val thyname = #thyname vs    (* ask for identifiers within a theory *)
   628         val objtype = #objtype vs    (* ask for identifiers of a particular type *)
   629 
   630         fun idtable ty ctx ids = {objtype=ty,context=ctx,ids=ids}
   631 
   632         fun setids t = issue_pgip (Setids {idtables = [t]})
   633 
   634         (* fake one-level nested "subtheories" by picking apart names. *)
   635         val thms_of_thy =
   636             map fst o NameSpace.extern_table o PureThy.theorems_of o ThyInfo.get_theory
   637         val immed_thms_of_thy = filter_out NameSpace.is_qualified o thms_of_thy
   638         fun thy_prefix s = case space_explode NameSpace.separator s of
   639                                     x::_::_ => SOME x  (* String.find? *)
   640                                   | _ => NONE
   641         fun subthys_of_thy s =
   642             List.foldl  (fn (NONE,xs) => xs | (SOME x,xs) => insert op= x xs) []
   643                    (map thy_prefix (thms_of_thy s))
   644         fun subthms_of_thy thy =
   645             (case thy_prefix thy of
   646                  NONE => immed_thms_of_thy thy
   647                | SOME prf => filter (String.isPrefix (unprefix (prf ^ NameSpace.separator) thy))
   648                                     (thms_of_thy prf))
   649        val qualified_thms_of_thy = (* for global query with single response *)
   650             (map fst) o NameSpace.dest_table o PureThy.theorems_of o ThyInfo.get_theory; 
   651 (* da: this version is equivalent to my previous, but splits up theorem sets with names
   652    that I can't get to access later with get_thm.  Anyway, would rather use sets.
   653    Is above right way to get qualified names in that case?  Filtering required again?
   654             map PureThy.get_name_hint o filter PureThy.has_name_hint o
   655               map snd o PureThy.thms_of o ThyInfo.get_theory; *)
   656     in 
   657         case (thyname,objtype) of
   658            (NONE, NONE) =>
   659            setids (idtable ObjFile NONE (ThyInfo.names())) (*FIXME: uris*)
   660          | (NONE, SOME ObjFile) =>
   661            setids (idtable ObjFile NONE (ThyInfo.names())) (*FIXME: uris*)
   662          | (SOME fi, SOME ObjFile) =>
   663            setids (idtable ObjTheory (SOME fi) [fi])       (* TODO: check exists *)
   664          | (NONE, SOME ObjTheory) =>
   665            setids (idtable ObjTheory NONE (ThyInfo.names()))
   666          | (SOME thy, SOME ObjTheory) =>
   667            setids (idtable ObjTheory (SOME thy) (subthys_of_thy thy))
   668          | (SOME thy, SOME ObjTheorem) =>
   669            setids (idtable ObjTheorem (SOME thy) (subthms_of_thy thy))
   670          | (NONE, SOME ObjTheorem) =>
   671            (* A large query, but not unreasonable. ~5000 results for HOL.*)
   672            (* Several setids should be allowed, but Eclipse code is currently broken:
   673               List.app (fn thy => setids (idtable ObjTheorem (SOME thy) (subthms_of_thy thy)))
   674                          (ThyInfo.names()) *)
   675            setids (idtable ObjTheorem NONE (* this one gives ~7000 for HOL *)
   676                            (maps qualified_thms_of_thy (ThyInfo.names())))
   677          | _ => warning ("askids: ignored argument combination")
   678     end
   679 
   680 fun askrefs (Askrefs vs) =
   681     let
   682         val url = #url vs            (* ask for references of a file (i.e. immediate pre-requisites) *)
   683         val thyname = #thyname vs    (* ask for references of a theory (other theories) *)
   684         val objtype = #objtype vs    (* ask for references of a particular type... *)
   685         val name = #name vs          (*   ... with this name *)
   686 
   687         fun idtable ty ctx ids = {objtype=ty,context=ctx,ids=ids}
   688 
   689         val thms_of_thy = map fst o PureThy.thms_of o ThyInfo.get_theory
   690 
   691         val thy_name = Path.implode o #1 o Path.split_ext o Path.base
   692 
   693         fun filerefs f =
   694             let val thy = thy_name f
   695                 val filerefs = #uses (ThyLoad.deps_thy (Path.dir f) thy)
   696             in
   697                 issue_pgip (Setrefs {url=url, thyname=NONE, objtype=SOME PgipTypes.ObjFile,
   698                                      name=NONE, idtables=[], fileurls=filerefs})
   699             end
   700 
   701         fun thyrefs thy =
   702             let val thyrefs = #imports (ThyLoad.deps_thy Path.current thy)
   703             in
   704                 issue_pgip (Setrefs {url=url, thyname=thyname, objtype=SOME PgipTypes.ObjTheory,
   705                                      name=NONE, idtables=[{context=NONE, objtype=PgipTypes.ObjTheory,
   706                                                            ids=thyrefs}], fileurls=[]})
   707             end
   708 
   709         fun thmrefs thmname =
   710             let
   711                 (* TODO: interim: this is probably not right.
   712                    What we want is mapping onto simple PGIP name/context model. *)
   713                 val ctx = Toplevel.context_of (Toplevel.get_state()) (* NB: raises UNDEF *)
   714                 val thy = Context.theory_of_proof ctx
   715                 val ths = [PureThy.get_thm thy (PureThy.Name thmname)]
   716                 val deps = filter_out (equal "")
   717                                       (Symtab.keys (fold Proofterm.thms_of_proof
   718                                                          (map Thm.proof_of ths) Symtab.empty))
   719             in
   720                 if null deps then ()
   721                 else issue_pgip (Setrefs {url=url, thyname=thyname, name=name,
   722                                           objtype=SOME PgipTypes.ObjTheorem,
   723                                           idtables=[{context=NONE, objtype=PgipTypes.ObjTheorem,
   724                                                      ids=deps}], fileurls=[]})
   725             end
   726     in
   727         case (url,thyname,objtype,name) of
   728             (SOME file, NONE, _, _)  => filerefs file
   729           | (_,SOME thy,_,_)         => thyrefs thy
   730           | (_,_,SOME PgipTypes.ObjTheorem,SOME thmname) => thmrefs thmname
   731           | _  => error ("Unimplemented/invalid case of <askrefs>")
   732     end
   733 
   734 
   735 
   736 fun showid (Showid vs) =
   737     let
   738         val thyname = #thyname vs
   739         val objtype = #objtype vs
   740         val name = #name vs
   741 
   742         val topthy = Toplevel.theory_of o Toplevel.get_state
   743 
   744         fun splitthy id =
   745             let val comps = NameSpace.explode id
   746             in case comps of
   747                    (thy::(rest as _::_)) => (ThyInfo.get_theory thy, space_implode "." rest)
   748                  | [plainid] => (topthy(),plainid)
   749                  | _ => raise Toplevel.UNDEF (* assert false *)
   750             end 
   751                                             
   752 
   753         fun idvalue strings =
   754             issue_pgip (Idvalue { thyname=thyname, objtype=objtype, name=name, 
   755                                   text=[XML.Elem("pgml",[],
   756                                                  map XML.Output strings)] })
   757 
   758         fun string_of_thm th = Output.output
   759                                (Pretty.string_of
   760                                    (Display.pretty_thm_aux
   761                                         (Sign.pp (Thm.theory_of_thm th))
   762                                         false (* quote *)
   763                                         false (* show hyps *)
   764                                         [] (* asms *)
   765                                         th))
   766 
   767         fun strings_of_thm (thy, name) = map string_of_thm (get_thms thy (Name name))
   768 
   769         val string_of_thy = Output.output o
   770                                 Pretty.string_of o (ProofDisplay.pretty_full_theory false)
   771     in
   772         case (thyname, objtype) of
   773             (_,ObjTheory) => idvalue [string_of_thy (ThyInfo.get_theory name)]
   774           | (SOME thy, ObjTheorem) => idvalue (strings_of_thm (ThyInfo.get_theory thy, name))
   775           | (NONE, ObjTheorem) => idvalue (strings_of_thm (splitthy name))
   776           | (_, ot) => error ("Cannot show objects of type "^(PgipTypes.name_of_objtype ot))
   777     end
   778 
   779 (*** Inspecting state ***)
   780 
   781 (* The file which is currently being processed interactively.
   782    In the pre-PGIP code, this was informed to Isabelle and the theory loader
   783    on completion, but that allows for circularity in case we read
   784    ourselves.  So PGIP opens the filename at the start of a script.
   785    We ought to prevent problems by modifying the theory loader to know
   786    about this special status, but for now we just keep a local reference.
   787 *)
   788 
   789 val currently_open_file = ref (NONE : pgipurl option)
   790 
   791 fun get_currently_open_file () = ! currently_open_file;
   792 
   793 fun askguise _ =
   794     (* The "guise" is the PGIP abstraction of the prover's state.
   795        The <informguise> message is merely used for consistency checking. *)
   796     let
   797         val openfile = !currently_open_file
   798 
   799         val topthy = Toplevel.theory_of o Toplevel.get_state
   800         val topthy_name = Context.theory_name o topthy
   801 
   802         val opentheory = SOME (topthy_name()) handle Toplevel.UNDEF => NONE
   803 
   804         fun topproofpos () = try Toplevel.proof_position_of (Isar.state ());
   805         val openproofpos = topproofpos()
   806     in
   807         issue_pgip (Informguise { file = openfile,
   808                                   theory = opentheory,
   809                                   (* would be nice to get thm name... *)
   810                                   theorem = NONE,
   811                                   proofpos = openproofpos })
   812     end
   813 
   814 fun parsescript (Parsescript vs) =
   815     let
   816         val text = #text vs
   817         val systemdata = #systemdata vs
   818         val location = #location vs   (* TODO: extract position *)
   819 
   820         val _ = start_delay_msgs ()   (* gather parsing errs/warns *)
   821 		    val doc = PgipParser.pgip_parser Position.none text
   822         val errs = end_delayed_msgs ()
   823 
   824         val sysattrs = PgipTypes.opt_attr "systemdata" systemdata
   825         val locattrs = PgipTypes.attrs_of_location location
   826      in
   827         issue_pgip (Parseresult { attrs= sysattrs@locattrs,
   828                                   doc = doc,
   829                                   errs = map PgipOutput.output errs })
   830     end
   831 
   832 fun showproofstate _ = isarcmd "pr"
   833 
   834 fun showctxt _ = isarcmd "print_context"
   835 
   836 fun searchtheorems (Searchtheorems vs) =
   837     let
   838         val arg = #arg vs
   839     in
   840         isarcmd ("find_theorems " ^ arg)
   841     end
   842 
   843 fun setlinewidth (Setlinewidth vs) =
   844     let
   845         val width = #width vs
   846     in
   847         isarcmd ("pretty_setmargin " ^ Int.toString width) (* FIXME: conversion back/forth! *)
   848     end
   849 
   850 fun viewdoc (Viewdoc vs) =
   851     let
   852         val arg = #arg vs
   853     in
   854         isarcmd ("print_" ^ arg)   (* FIXME: isatool doc?.  Return URLs, maybe? *)
   855     end
   856 
   857 (*** Theory ***)
   858 
   859 fun doitem (Doitem vs) =
   860     let
   861         val text = #text vs
   862     in
   863         isarcmd text
   864     end
   865 
   866 fun undoitem _ =
   867     isarcmd "undo"
   868 
   869 fun redoitem _ =
   870     isarcmd "redo"
   871 
   872 fun aborttheory _ =
   873     isarcmd "kill"  (* was: "init_toplevel" *)
   874 
   875 fun retracttheory (Retracttheory vs) =
   876     let
   877         val thyname = #thyname vs
   878     in
   879         isarcmd ("kill_thy " ^ quote thyname)
   880     end
   881 
   882 
   883 (*** Files ***)
   884 
   885 (* Path management: we allow theory files to have dependencies in
   886    their own directory, but when we change directory for a new file we
   887    remove the path.  Leaving it there can cause confusion with
   888    difference in batch mode.
   889    NB: PGIP does not assume that the prover has a load path.
   890 *)
   891 
   892 local
   893     val current_working_dir = ref (NONE : string option)
   894 in
   895 fun changecwd_dir newdirpath =
   896    let
   897        val newdir = File.platform_path newdirpath
   898    in
   899        (case (!current_working_dir) of
   900             NONE => ()
   901           | SOME dir => ThyLoad.del_path dir;
   902         ThyLoad.add_path newdir;
   903         current_working_dir := SOME newdir)
   904    end
   905 end
   906 
   907 fun changecwd (Changecwd vs) =
   908     let
   909         val url = #url vs
   910         val newdir = PgipTypes.path_of_pgipurl url
   911     in
   912         changecwd_dir url
   913     end
   914 
   915 fun openfile (Openfile vs) =
   916   let
   917       val url = #url vs
   918       val filepath = PgipTypes.path_of_pgipurl url
   919       val filedir = Path.dir filepath
   920       val thy_name = Path.implode o #1 o Path.split_ext o Path.base
   921       val openfile_retract = Output.no_warnings (ThyInfo.if_known_thy ThyInfo.remove_thy) o thy_name;
   922   in
   923       case !currently_open_file of
   924           SOME f => raise PGIP ("<openfile> when a file is already open!\nCurrently open file: " ^
   925                                 PgipTypes.string_of_pgipurl url)
   926         | NONE => (openfile_retract filepath;
   927                    changecwd_dir filedir;
   928                    priority ("Working in file: " ^ PgipTypes.string_of_pgipurl url);
   929                    currently_open_file := SOME url)
   930   end
   931 
   932 fun closefile _ =
   933     case !currently_open_file of
   934         SOME f => (proper_inform_file_processed f (Isar.state());
   935                    priority ("Finished working in file: " ^ PgipTypes.string_of_pgipurl f);
   936                    currently_open_file := NONE)
   937       | NONE => raise PGIP ("<closefile> when no file is open!")
   938 
   939 fun loadfile (Loadfile vs) =
   940     let
   941         val url = #url vs
   942     in
   943         (* da: this doesn't seem to cause a problem, batch loading uses
   944            a different state context.  Of course confusion is still possible,
   945            e.g. file loaded depends on open file which is not yet saved. *)
   946         (* case !currently_open_file of
   947             SOME f => raise PGIP ("<loadfile> when a file is open!\nCurrently open file: " ^
   948                                   PgipTypes.string_of_pgipurl url)
   949           | NONE => *)
   950         use_thy_or_ml_file (File.platform_path url)
   951     end
   952 
   953 fun abortfile _ =
   954     case !currently_open_file of
   955         SOME f => (isarcmd "init_toplevel";
   956                    priority ("Aborted working in file: " ^
   957                              PgipTypes.string_of_pgipurl f);
   958                    currently_open_file := NONE)
   959       | NONE => raise PGIP ("<abortfile> when no file is open!")
   960 
   961 fun retractfile (Retractfile vs) =
   962     let
   963         val url = #url vs
   964     in
   965         case !currently_open_file of
   966             SOME f => raise PGIP ("<retractfile> when a file is open!")
   967           | NONE => (priority ("Retracting file: " ^ PgipTypes.string_of_pgipurl url);
   968                      (* TODO: next should be in thy loader, here just for testing *)
   969                      let
   970                          val name = thy_name url
   971                      in List.app (tell_file_retracted false) (ThyInfo.loaded_files name) end;
   972                      inform_file_retracted url)
   973     end
   974 
   975 
   976 (*** System ***)
   977 
   978 fun systemcmd (Systemcmd vs) =
   979   let
   980       val arg = #arg vs
   981   in
   982       isarcmd arg
   983   end
   984 
   985 exception PGIP_QUIT;
   986 fun quitpgip _ = raise PGIP_QUIT
   987 
   988 fun process_input inp = case inp
   989  of Pgip.Askpgip _          => askpgip inp
   990   | Pgip.Askpgml _          => askpgml inp
   991   | Pgip.Askprefs _         => askprefs inp
   992   | Pgip.Askconfig _        => askconfig inp
   993   | Pgip.Getpref _          => getpref inp
   994   | Pgip.Setpref _          => setpref inp
   995   | Pgip.Proverinit _       => proverinit inp
   996   | Pgip.Proverexit _       => proverexit inp
   997   | Pgip.Setproverflag _    => setproverflag inp
   998   | Pgip.Dostep _           => dostep inp
   999   | Pgip.Undostep _         => undostep inp
  1000   | Pgip.Redostep _         => redostep inp
  1001   | Pgip.Forget _           => error "<forget> not implemented by Isabelle"
  1002   | Pgip.Restoregoal _      => error "<restoregoal> not implemented by Isabelle"
  1003   | Pgip.Abortgoal _        => abortgoal inp
  1004   | Pgip.Askids _           => askids inp
  1005   | Pgip.Askrefs _          => askrefs inp
  1006   | Pgip.Showid _           => showid inp
  1007   | Pgip.Askguise _         => askguise inp
  1008   | Pgip.Parsescript _      => parsescript inp
  1009   | Pgip.Showproofstate _   => showproofstate inp
  1010   | Pgip.Showctxt _         => showctxt inp
  1011   | Pgip.Searchtheorems _   => searchtheorems inp
  1012   | Pgip.Setlinewidth _     => setlinewidth inp
  1013   | Pgip.Viewdoc _          => viewdoc inp
  1014   | Pgip.Doitem _           => doitem inp
  1015   | Pgip.Undoitem _         => undoitem inp
  1016   | Pgip.Redoitem _         => redoitem inp
  1017   | Pgip.Aborttheory _      => aborttheory inp
  1018   | Pgip.Retracttheory _    => retracttheory inp
  1019   | Pgip.Loadfile _         => loadfile inp
  1020   | Pgip.Openfile _         => openfile inp
  1021   | Pgip.Closefile _        => closefile inp
  1022   | Pgip.Abortfile _        => abortfile inp
  1023   | Pgip.Retractfile _      => retractfile inp
  1024   | Pgip.Changecwd _        => changecwd inp
  1025   | Pgip.Systemcmd _        => systemcmd inp
  1026   | Pgip.Quitpgip _         => quitpgip inp
  1027 
  1028 
  1029 fun process_pgip_element pgipxml =
  1030     case pgipxml of
  1031         xml as (XML.Elem elem) =>
  1032         (case Pgip.input elem of
  1033              NONE => warning ("Unrecognized PGIP command, ignored: \n" ^
  1034                               (XML.string_of_tree xml))
  1035            | SOME inp => (process_input inp)) (* errors later; packet discarded *)
  1036       | XML.Text t => ignored_text_warning t
  1037       | XML.Output t => ignored_text_warning t
  1038 and ignored_text_warning t =
  1039     if size (Symbol.strip_blanks t) > 0 then
  1040            warning ("Ignored text in PGIP packet: \n" ^ t)
  1041     else ()
  1042 
  1043 fun process_pgip_tree xml =
  1044     (pgip_refid := NONE;
  1045      pgip_refseq := NONE;
  1046      (case xml of
  1047           XML.Elem ("pgip", attrs, pgips) =>
  1048           (let
  1049                val class = PgipTypes.get_attr "class" attrs
  1050                val dest  = PgipTypes.get_attr_opt "destid" attrs
  1051                val seq = PgipTypes.read_pgipnat (PgipTypes.get_attr "seq" attrs)
  1052                (* Respond to prover broadcasts, or messages for us. Ignore rest *)
  1053                val processit =
  1054                    case dest of
  1055                        NONE =>    class = "pa"
  1056                      | SOME id => matching_pgip_id id
  1057            in if processit then
  1058                   (pgip_refid :=  PgipTypes.get_attr_opt "id" attrs;
  1059                    pgip_refseq := SOME seq;
  1060                    List.app process_pgip_element pgips;
  1061                    (* return true to indicate <ready/> *)
  1062                    true)
  1063               else
  1064                   (* no response to ignored messages. *)
  1065                   false
  1066            end)
  1067         | _ => raise PGIP "Invalid PGIP packet received")
  1068      handle PGIP msg =>
  1069             (Output.error_msg ((msg ^ "\nPGIP error occured in XML text below:\n") ^
  1070                                (XML.string_of_tree xml));
  1071              true))
  1072 
  1073 (* External input *)
  1074 
  1075 val process_pgip_plain = K () o process_pgip_tree o XML.tree_of_string
  1076 
  1077 (* PGIP loop: process PGIP input only *)
  1078 
  1079 local
  1080 
  1081 exception XML_PARSE
  1082 
  1083 fun loop ready src =
  1084     let
  1085         val _ = if ready then issue_pgip (Ready ()) else ()
  1086         val pgipo =
  1087           (case try Source.get_single src of
  1088             SOME pgipo => pgipo
  1089           | NONE => raise XML_PARSE)
  1090     in
  1091         case pgipo of
  1092              NONE  => ()
  1093            | SOME (pgip,src') =>
  1094              let
  1095                  val ready' = (process_pgip_tree pgip)
  1096                                 handle PGIP_QUIT => raise PGIP_QUIT
  1097                                      | e => (handler (e,SOME src'); true)
  1098              in
  1099                  loop ready' src'
  1100              end
  1101     end handle e => handler (e,SOME src)  (* error in XML parse or Ready issue *)
  1102 
  1103 and handler (e,srco) =
  1104     case (e,srco) of
  1105         (XML_PARSE,SOME src) =>
  1106         panic "Invalid XML input, aborting" (* TODO: attempt recovery  *)
  1107       | (Interrupt,SOME src) =>
  1108         (Output.error_msg "Interrupt during PGIP processing"; loop true src)
  1109       | (Toplevel.UNDEF,SOME src) =>
  1110         (Output.error_msg "No working context defined"; loop true src)
  1111       | (e,SOME src) =>
  1112         (Output.error_msg (Toplevel.exn_message e); loop true src)
  1113       | (PGIP_QUIT,_) => ()
  1114       | (_,NONE) => ()
  1115 in
  1116   (* TODO: add socket interface *)
  1117 
  1118   val xmlP = XML.scan_comment_whspc |-- XML.parse_elem >> single
  1119 
  1120   val tty_src = Source.set_prompt "" (Source.source Symbol.stopper xmlP NONE Source.tty)
  1121 
  1122   fun pgip_toplevel x = loop true x
  1123 end
  1124 
  1125 
  1126 (* Extra command for embedding prover-control inside document (obscure/debug usage). *)
  1127 
  1128 fun init_outer_syntax () =
  1129   OuterSyntax.improper_command "ProofGeneral.process_pgip" "(internal)" OuterKeyword.control
  1130     (OuterParse.text >> (Toplevel.no_timing oo
  1131       (fn txt => Toplevel.imperative (fn () => process_pgip_plain txt))));
  1132 
  1133 
  1134 (* init *)
  1135 
  1136 val initialized = ref false;
  1137 
  1138 fun init_pgip false = panic "No Proof General interface support for Isabelle/classic mode."
  1139   | init_pgip true =
  1140       (! initialized orelse
  1141         (setup_preferences_tweak ();
  1142          setup_proofgeneral_output ();
  1143          setup_messages ();
  1144          Output.no_warnings init_outer_syntax ();
  1145          setup_thy_loader ();
  1146          setup_present_hook ();
  1147          init_pgip_session_id ();
  1148          welcome ();
  1149          set initialized);
  1150         sync_thy_loader ();
  1151        change print_mode (cons proof_generalN o remove (op =) proof_generalN);
  1152        pgip_toplevel tty_src);
  1153 
  1154 
  1155 
  1156 (** Out-of-loop PGIP commands (for Emacs hybrid mode) **)
  1157 
  1158 local
  1159     val pgip_output_channel = ref Output.writeln_default
  1160 in
  1161 
  1162 (* Set recipient for PGIP results *)
  1163 fun init_pgip_channel writefn =
  1164     (init_pgip_session_id();
  1165      pgip_output_channel := writefn)
  1166 
  1167 (* Process a PGIP command.
  1168    This works for preferences but not generally guaranteed
  1169    because we haven't done full setup here (e.g., no pgml mode)  *)
  1170 fun process_pgip str =
  1171      setmp output_xml_fn (!pgip_output_channel) process_pgip_plain str
  1172 
  1173 end
  1174 
  1175 end;