src/Pure/proof_general.ML
author wenzelm
Sun, 22 May 2005 16:51:10 +0200
changeset 16022 96a9bf7ac18d
parent 15992 cb02d70a2040
child 16259 aed1a8ae4b23
permissions -rw-r--r--
FindTheorems.print_theorems;

(*  Title:      Pure/proof_general.ML
    ID:         $Id$
    Author:     David Aspinall and Markus Wenzel

Isabelle configuration for Proof General (see http://proofgeneral.inf.ed.ac.uk)
Includes support for PGIP control language for Isabelle/Isar.

===========================================================================
NOTE: With this version you will lose support for the Isabelle
settings menu in the currently released version of Proof General (3.5).
No other changes should be visible in the Emacs interface.

The 3.6pre pre-release versions of Emacs Proof General now support the 
new PGIP format for preferences and restore the settings menu.  
Please visit http://proofgeneral.inf.ed.ac.uk/develdownload
===========================================================================

STATUS: this version is an experimental version that supports PGIP 2.X.

*)

signature PROOF_GENERAL =
sig
  val update_thy_only: string -> unit
  val try_update_thy_only: string -> unit
  val inform_file_retracted: string -> unit
  val inform_file_processed: string -> unit
  val preferences: 
      (string * 
	(string * 
	 (string * 
	  (string * (string * (unit -> string)) * 
	   (string -> unit)))) list) list ref
  val process_pgip: string -> unit
  val thms_containing: string list -> unit
  val help: unit -> unit
  val show_context: unit -> theory
  val kill_goal: unit -> unit
  val repeat_undo: int -> unit
  val isa_restart: unit -> unit
  val full_proofs: bool -> unit
  val isarcmd: string -> unit
  val init: bool -> unit
  val init_pgip: bool -> unit
  val write_keywords: string -> unit
  val xmls_of_str : string -> string list (* temp for testing parser *)
end;

structure ProofGeneral : PROOF_GENERAL =
struct

structure P = OuterParse


(* PGIP messaging mode (independent of PGML output) *)

val pgip_active = ref false;
val pgip_emacs_compatibility_flag = ref false;

fun pgip () = (!pgip_active);
fun pgip_emacs_compatibility () = (!pgip_emacs_compatibility_flag);


(* print modes *)

val proof_generalN = "ProofGeneral"; (* token markup (colouring vars, etc.) *)
val xsymbolsN = Symbol.xsymbolsN;    (* XSymbols symbols *)
val pgmlN = "PGML";		     (* XML escapes and PGML symbol elements *)
val pgmlatomsN = "PGMLatoms";	     (* variable kind decorations *)
val thm_depsN = "thm_deps";	     (* meta-information about theorem deps *)

fun pgml () = pgmlN mem_string ! print_mode;

(* text output: print modes for xsymbol and PGML *)

local

fun xsym_output "\\" = "\\\\"
  | xsym_output s = if Symbol.is_raw s then Symbol.decode_raw s else s;

fun xsymbols_output s =
  if xsymbolsN mem_string ! print_mode andalso exists_string (equal "\\") s then
    let val syms = Symbol.explode s
    in (implode (map xsym_output syms), real (Symbol.length syms)) end
  else Symbol.default_output s;

fun pgml_sym s =
  (case Symbol.decode s of
    (* NB: an alternative here would be to output the default print mode alternative
       in the element content, but unfortunately print modes are not that fine grained. *)
    Symbol.Char s => XML.text s
  | Symbol.Sym sn => XML.element "sym" [("name", sn)] [XML.text s]
  | Symbol.Ctrl sn => XML.element "ctrl" [("name", sn)] [XML.text s]   (* FIXME: no such PGML! *)
  | Symbol.Raw s => s);

fun pgml_output str =
  let val syms = Symbol.explode str
  in (implode (map pgml_sym syms), real (Symbol.length syms)) end;

in 

fun setup_xsymbols_output () = 
    Output.add_mode 
	xsymbolsN 
	(xsymbols_output, Symbol.default_indent, Symbol.encode_raw);

fun setup_pgml_output () = 
    Output.add_mode 
	pgmlN
	(pgml_output, Symbol.default_indent, Symbol.encode_raw);

end;


(* token translations *)

local

val end_tag = oct_char "350";
val class_tag = ("class", oct_char "351");
val tfree_tag = ("tfree", oct_char "352");
val tvar_tag = ("tvar", oct_char "353");
val free_tag = ("free", oct_char "354");
val bound_tag = ("bound", oct_char "355");
val var_tag = ("var", oct_char "356");
val skolem_tag = ("skolem", oct_char "357");

fun xml_atom kind x = XML.element "atom" [("kind", kind)] [XML.text x];

fun tagit (kind, bg_tag) x =
    (if Output.has_mode pgmlatomsN then 
	 xml_atom kind x
     else bg_tag ^ x ^ end_tag, 
     real (Symbol.length (Symbol.explode x)));

fun free_or_skolem x =
  (case try Syntax.dest_skolem x of
    NONE => tagit free_tag x
  | SOME x' => tagit skolem_tag x');

fun var_or_skolem s =
  (case Syntax.read_variable s of
    SOME (x, i) =>
      (case try Syntax.dest_skolem x of
        NONE => tagit var_tag s
      | SOME x' => tagit skolem_tag
          (setmp show_question_marks true Syntax.string_of_vname (x', i)))
  | NONE => tagit var_tag s);

val proof_general_trans =
 Syntax.tokentrans_mode proof_generalN
  [("class", tagit class_tag),
   ("tfree", tagit tfree_tag),
   ("tvar", tagit tvar_tag),
   ("free", free_or_skolem),
   ("bound", tagit bound_tag),
   ("var", var_or_skolem)];

in

val _ = Context.add_setup [Theory.add_tokentrfuns proof_general_trans];

end;


(* assembling PGIP packets *)

val pgip_refseq = ref NONE : string option ref
val pgip_refid = ref NONE : string option ref

local
    val myseq_no = ref 1;      (* PGIP packet counter *)

    val pgip_class  = "pg"
    val pgip_origin = "Isabelle/Isar"
    val pgip_id = (getenv "HOSTNAME") ^ "/" ^ (getenv "USER") ^ "/" ^ 
		   (Time.toString(Time.now()))
	          (* FIXME: PPID is empty for me: any way to get process ID? *)

    fun assemble_pgips pgips = 
	XML.element 
	     "pgip" 
	     ([("class",  pgip_class),
	       ("origin", pgip_origin),
	       ("id",     pgip_id)] @
	      getOpt (Option.map (single o (pair "refseq")) (!pgip_refseq), []) @
	      getOpt (Option.map (single o (pair "refid")) (!pgip_refid), []) @
	      [("seq",  string_of_int (Library.inc myseq_no))])
	     pgips
in

 fun decorated_output bg en prfx = 
    writeln_default o enclose bg en o prefix_lines prfx;

 (* FIXME: see Rev 1.48 [PG CVS] for cleaner version of this which can be used
    for PG 4.0 which processes PGIP without needing special chars. *)
 fun issue_pgips ps = 
     if pgip_emacs_compatibility() then
	 decorated_output (* add urgent message annotation *)
	     (oct_char "360") (oct_char "361") "" 
	     (assemble_pgips ps)
     else 
	 writeln_default (assemble_pgips ps)

 fun assemble_pgip resp attrs s = assemble_pgips [XML.element resp attrs [s]]
				  
 fun assemble_pgipe resp attrs = assemble_pgips [XML.element resp attrs []]

 (* FIXME: need to add stuff to gather PGIPs here *)
 fun issue_pgip resp attrs txt = 
     if pgip_emacs_compatibility() then
	 decorated_output (* add urgent message annotation *)
	     (oct_char "360") (oct_char "361") "" 
	     (assemble_pgip resp attrs txt)
     else 
	 writeln_default (assemble_pgip resp attrs txt)

(*  FIXME: temporarily to support PG 3.4.  *)
 fun issue_pgip_maybe_decorated bg en resp attrs s = 
     if pgip_emacs_compatibility() then
        (*  in PGIP mode, but using escape characters as well.  *)
	writeln_default (enclose bg en (assemble_pgip resp attrs s))
     else 
	issue_pgip resp attrs s
	
 fun issue_pgipe resp attrs = writeln_default (assemble_pgipe resp attrs)

end

(* messages and notification *)

local
    val delay_msgs = ref false   (* whether to accumulate messages *)
    val delayed_msgs = ref []
in

 fun message resp attrs bg en prfx s =
     if pgip() then 
	 (if (!delay_msgs) then
	      delayed_msgs := (resp,attrs,prefix_lines prfx s)::(!delayed_msgs) (* nb: no decs *)
	  else 
	      issue_pgip_maybe_decorated bg en resp attrs 
					 (XML.element "pgmltext" [] [prefix_lines prfx s]))
     else 
	 decorated_output bg en prfx s;

 fun start_delay_msgs () = (delay_msgs := true;
			    delayed_msgs := [])

 fun end_delayed_msgs () = 
     (delay_msgs := false;
      map (fn (resp,attrs,s) => XML.element resp attrs [XML.element "pgmltext" [] [s]]) (!delayed_msgs))
end

local
    val display_area = ("area", "display")
    val message_area = ("area", "message")
    val internal_category = ("messagecategory", "internal")
    val info_category = ("messagecategory", "information")
    val tracing_category = ("messagecategory", "tracing")
    val urgent_indication = ("urgent", "y")
    val nonfatal = ("fatality", "nonfatal")
    val fatal = ("fatality", "fatal")
    val panic = ("fatality", "panic")

    val thyname_attr = pair "thyname"
    val url_attr = pair "url"
    fun localfile_url_attr path = url_attr ("file:///" ^ path)
in			   

fun setup_messages () =
 (writeln_fn  := message "normalresponse" [message_area] "" "" "";

  priority_fn := message "normalresponse" [message_area, urgent_indication]
			 (oct_char "360") (oct_char "361") "";

  tracing_fn := message "normalresponse"  [message_area, tracing_category]
			(oct_char "360" ^ oct_char "375") (oct_char "361") "";

  info_fn := message "normalresponse" [message_area, info_category]
			(oct_char "362") (oct_char "363") "+++ ";

  debug_fn := message "normalresponse" [message_area, internal_category]
			(oct_char "362") (oct_char "363") "+++ ";

  warning_fn := message "errorresponse"    [nonfatal]
			(oct_char "362") (oct_char "363") "### ";

  error_fn := message "errorresponse" [fatal]
		      (oct_char "364") (oct_char "365") "*** ";

  panic_fn := message "errorresponse" [panic]
		      (oct_char "364") (oct_char "365") "!!! ")


(* accumulate printed output in a single PGIP response (inside <pgmltext>) *)

fun with_displaywrap (elt,attrs) dispfn =
    let 
	val lines = ref ([] : string list);
	fun wlgrablines s = (lines:= (s :: (!lines)))
    in 
	(setmp writeln_fn wlgrablines dispfn ();
	 (* FIXME: XML.element here inefficient, should use stream output *)
         issue_pgip elt attrs (XML.element "pgmltext" [] (!lines)))
    end

val emacs_notify = decorated_output (oct_char "360") (oct_char "361") "";
    
fun tell_clear_goals () = 
    if pgip() then
	issue_pgipe "cleardisplay" [display_area]
    else 
	emacs_notify "Proof General, please clear the goals buffer.";

fun tell_clear_response () = 
    if pgip() then
	issue_pgipe "cleardisplay" [message_area]
    else 
	emacs_notify "Proof General, please clear the response buffer.";

fun tell_file_loaded path = 
    if pgip() then
	issue_pgipe "informtheoryloaded"  (* FIXME: get thy name from info here? *)
		    [thyname_attr        (XML.text (File.sysify_path path)),
		     localfile_url_attr  (XML.text (File.sysify_path path))]
    else 
	emacs_notify ("Proof General, this file is loaded: " 
		      ^ quote (File.sysify_path path));

fun tell_file_retracted path = 
    if pgip() then
	issue_pgipe "informtheoryretracted"  (* FIXME: get thy name from info here? *)
		    [thyname_attr        (XML.text (File.sysify_path path)),
		     localfile_url_attr  (XML.text (File.sysify_path path))]
    else 
	emacs_notify ("Proof General, you can unlock the file " 
		      ^ quote (File.sysify_path path));


(* theory / proof state output *)

local

    fun tmp_markers f =	setmp Display.current_goals_markers 
			      (oct_char "366", oct_char "367", "") f ()

    fun statedisplay prts =
	issue_pgip "proofstate" []
		   (XML.element "pgml" []
				[XML.element "statedisplay" 
					     [] 
					     [(Pretty.output (Pretty.chunks prts))]])

    fun print_current_goals n m st =
	if pgml () then statedisplay (Display.pretty_current_goals n m st)
	else tmp_markers (fn () => Display.print_current_goals_default n m st)
	     
    fun print_state b st =
	if pgml () then statedisplay (Toplevel.pretty_state b st)
	else tmp_markers (fn () => Toplevel.print_state_default b st)
in

 fun setup_state () =
     (Display.print_current_goals_fn := print_current_goals;
      Toplevel.print_state_fn := print_state;
      (* FIXME: check next octal char won't appear in pgip? *)
      Toplevel.prompt_state_fn := (suffix (oct_char "372") o  
				   Toplevel.prompt_state_default));
end

end


(* misc commands for ProofGeneral/isa *)

fun thms_containing ss =
  FindTheorems.print_theorems (ProofContext.init (the_context ())) NONE NONE 
    (map (fn s => (true, FindTheorems.Pattern s)) ss);

val welcome = priority o Session.welcome;
val help = welcome;
val show_context = Context.the_context;

fun kill_goal () = (Goals.reset_goals (); tell_clear_goals ());

fun no_print_goals f = setmp Display.print_current_goals_fn (fn _ => fn _ => fn _ => ()) f;

fun repeat_undo 0 = ()
  | repeat_undo 1 = undo ()
  | repeat_undo n = (no_print_goals undo (); repeat_undo (n - 1));


(* theory loader actions *)

local

fun add_master_files name files =
  let val masters = [ThyLoad.thy_path name, ThyLoad.ml_path name]
  in masters @ gen_rems (op = o pairself Path.base) (files, masters) end;

fun trace_action action name =
  if action = ThyInfo.Update then
    List.app tell_file_loaded (ThyInfo.loaded_files name)
  else if action = ThyInfo.Outdate orelse action = ThyInfo.Remove then
    List.app tell_file_retracted (add_master_files name (ThyInfo.loaded_files name))
  else ();

in
  fun setup_thy_loader () = ThyInfo.add_hook trace_action;
  fun sync_thy_loader () = List.app (trace_action ThyInfo.Update) (ThyInfo.names ());
end;


(* prepare theory context *)

val thy_name = Path.pack o #1 o Path.split_ext o Path.base o Path.unpack;
val update_thy_only = setmp MetaSimplifier.trace_simp 
			    false ThyInfo.update_thy_only;

fun which_context () =
  (case Context.get_context () of
    SOME thy => "  Using current (dynamic!) one: " ^
      (case try PureThy.get_name thy of SOME name => quote name | NONE => "<unnamed>")
  | NONE => "");

fun try_update_thy_only file =
  ThyLoad.cond_add_path (Path.dir (Path.unpack file)) (fn () =>
    let val name = thy_name file in
      if isSome (ThyLoad.check_file NONE (ThyLoad.thy_path name)) 
      then update_thy_only name
      else warning ("Unkown theory context of ML file." ^ which_context ())
    end) ();


(* get informed about files *)

val inform_file_retracted = 
    ThyInfo.if_known_thy ThyInfo.remove_thy o thy_name;
val inform_file_processed = 
    ThyInfo.if_known_thy ThyInfo.touch_child_thys o thy_name;

fun if_known_thy_no_warning f name = if ThyInfo.known_thy name then f name else ();

val openfile_retract = 
    if_known_thy_no_warning ThyInfo.remove_thy o thy_name;

fun proper_inform_file_processed file state =
  let val name = thy_name file in
    if Toplevel.is_toplevel state andalso ThyInfo.known_thy name then
     (ThyInfo.touch_child_thys name;
      transform_error ThyInfo.pretend_use_thy_only name handle ERROR_MESSAGE msg =>
       (warning msg; warning ("Failed to register theory: " ^ quote name);
        tell_file_retracted (Path.base (Path.unpack file))))
    else raise Toplevel.UNDEF
  end;

fun vacuous_inform_file_processed file state =
 (warning ("No theory " ^ quote (thy_name file));
  tell_file_retracted (Path.base (Path.unpack file)));


(* restart top-level loop (keeps most state information) *)

local

fun restart isar =
 (if isar then tell_clear_goals () else kill_goal ();
  tell_clear_response ();
  welcome ());

in

fun isa_restart () = restart false;
fun isar_restart () = (sync_thy_loader (); restart true; raise Toplevel.RESTART);

end;

fun full_proofs true = proofs := 2
  | full_proofs false = proofs := 1;


(* theorem dependency output *)

local

val spaces_quote = space_implode " " o map quote;

fun thm_deps_message (thms, deps) = 
    if pgip() then
	issue_pgips 
	    [XML.element
		 "metainforesponse"  (* FIXME: get thy name from info here? *)
		 [("infotype", "isabelle_theorem_dependencies")]
		 [XML.element "value" [("name", "thms")] [XML.text thms],
		  XML.element "value" [("name", "deps")] [XML.text deps]]]
    else emacs_notify 
	     ("Proof General, theorem dependencies of " 
	      ^ thms ^ " are " ^ deps)


fun tell_thm_deps ths =
  conditional (thm_depsN mem_string ! print_mode) (fn () =>
    let
      val names = map Thm.name_of_thm ths \ "";
      val deps = Symtab.keys (Library.foldl (uncurry Proofterm.thms_of_proof)
				    (Symtab.empty, map Thm.proof_of ths)) \ "";
    in
      if null names orelse null deps then ()
      else thm_deps_message (spaces_quote names, spaces_quote deps)
    end);

in

fun setup_present_hook () =
  Present.add_hook (fn _ => fn res => tell_thm_deps (List.concat (map #2 res)));

end;


(*** Preferences ***)

local
val pgipnat        = XML.element "pgipint" [("min", "0")] [] 
fun pgipnatmax max = XML.element "pgipint" [("min", "0"), 
					    ("max", string_of_int max)] []
val pgipbool       = XML.element "pgipbool" [] []

fun withdefault f = (f(), f)
in

fun nat_option r = (pgipnat,
  withdefault (fn () => string_of_int (!r)),
  (fn s => (case Syntax.read_nat s of
       NONE => error ("nat_option: illegal value " ^ s)
     | SOME i => r := i)));

fun bool_option r = (pgipbool,
  withdefault (fn () => Bool.toString (!r)),
  (fn "false" => r := false | "true" => r := true
    | x => error ("bool_option: illegal value" ^ x)));

val proof_option = (pgipbool,
  withdefault (fn () => Bool.toString (!proofs >= 2)),
  (fn "false" => proofs := 1 | "true" => proofs := 2
    | x => error ("proof_option: illegal value" ^ x)));

val thm_deps_option = (pgipbool,
  withdefault (fn () => Bool.toString (Output.has_mode "thm_deps")),
  (fn "false" => print_mode := ((!print_mode) \ "thm_deps")
    | "true" => print_mode := ("thm_deps" ins (!print_mode))
    | x => error ("thm_deps_option: illegal value " ^ x)));

local 
    val pg_print_depth_val = ref 10
    fun pg_print_depth n = (print_depth n; pg_print_depth_val := n)
in
val print_depth_option = (pgipnat,
  withdefault (fn () => string_of_int (!pg_print_depth_val)),
  (fn s => (case Syntax.read_nat s of
       NONE => error ("print_depth_option: illegal value" ^ s)
     | SOME i => pg_print_depth i)))
end

val preferences = ref 
  [("Display",
    [("show-types", 
      ("Include types in display of Isabelle terms", 
       bool_option show_types)),
     ("show-sorts", 
      ("Include sorts in display of Isabelle terms", 
       bool_option show_sorts)),
     ("show-consts", 
      ("Show types of consts in Isabelle goal display",
       bool_option show_consts)),
     ("long-names", 
      ("Show fully qualified names in Isabelle terms", bool_option long_names)),
     ("show-brackets", 
      ("Show full bracketing in Isabelle terms",
       bool_option show_brackets)),
     ("show-main-goal", 
      ("Show main goal in proof state display", bool_option Proof.show_main_goal)),
     ("eta-contract", 
      ("Print terms eta-contracted",
       bool_option Syntax.eta_contract))]),
   ("Advanced Display",
    [("goals-limit", 
      ("Setting for maximum number of goals printed",
       nat_option goals_limit)),
     ("prems-limit", 
      ("Setting for maximum number of premises printed",
       nat_option ProofContext.prems_limit)),
     ("print-depth", 
      ("Setting for the ML print depth",
       print_depth_option)),
     ("show-question-marks",
      ("Show leading question mark of variable name",
       bool_option show_question_marks))]),
   ("Tracing",
    [("trace-simplifier", 
      ("Trace simplification rules.",
       bool_option trace_simp)),
     ("trace-rules", ("Trace application of the standard rules",
		      bool_option trace_rules)),
     ("trace-unification", 
      ("Output error diagnostics during unification",
       bool_option Pattern.trace_unify_fail)),
     ("global-timing", 
      ("Whether to enable timing in Isabelle.",
       bool_option Output.timing))]),
   ("Proof", 
    [("quick-and-dirty", 
      ("Take a few (interactive-only) short cuts",
       bool_option quick_and_dirty)),
     ("full-proofs", 
      ("Record full proof objects internally",
       proof_option)),
     ("theorem-dependencies", 
       ("Track theorem dependencies within Proof General",
	thm_deps_option)),
     ("skip-proofs", 
      ("Skip all proof scripts (interactive-only)",
       bool_option Toplevel.skip_proofs))])
   ];
end

(* Configuration: GUI config, proverinfo messages *)

local
    val config_file = "~~/lib/ProofGeneral/pgip_isar.xml"
    val name_attr = pair "name"
    val version_attr = pair "version"
    val isabelle_www = "http://isabelle.in.tum.de/"
in
fun send_pgip_config () =
    let 
	val path = Path.unpack config_file
	val exists = File.exists path
	val proverinfo = 
	    XML.element "proverinfo"
              [("name",Session.name()), ("version", version),
	       ("url", isabelle_www), ("filenameextns", ".thy;")]
	    [XML.element "welcomemsg" [] [XML.text (Session.welcome())],
	     XML.element "helpdoc" 
         (* NB: would be nice to generate/display docs from isatool
          doc, but that program may not be running on same machine;
          front end should be responsible for launching viewer, etc. *)
		      [("name", "Isabelle/HOL Tutorial"),
		       ("descr", "A Gentle Introduction to Isabelle/HOL"), 
		       ("url",  "http://isabelle.in.tum.de/dist/Isabelle2004/doc/tutorial.pdf")]
	      []]
    in
	if exists then
	    (issue_pgips [proverinfo]; issue_pgips [File.read path])
	else panic ("PGIP configuration file " ^ config_file ^ " not found")
    end;
end


(* Reveal some information about prover state *)
fun send_informguise (openfile, opentheory, openproofpos) =
    let val guisefile = 
	    case openfile of SOME f => [XML.element "guisefile" 
						    [("url",Url.pack (Url.File (Path.unpack f)))] []]
			   | _ => []
	val guisetheory = 
	    case opentheory of SOME t => [XML.element "guisetheory" [("thyname", t)] []]
			     | _ => []
	val guiseproof = 
	    case openproofpos of SOME i => [XML.element "guiseproof" [("proofpos", string_of_int i)] []]
			       | _ => []
    in 
	issue_pgips [XML.element "informguise" [] (guisefile @ guisetheory @ guiseproof)]
    end 


(* PGIP identifier tables (prover objects). [incomplete] *)

local
    val objtype_thy  = "theory"
    val objtype_thm  = "theorem"
    val objtype_term = "term"
    val objtype_type = "type"
		       
    fun xml_idtable ty ctx ids = 
	let
            fun ctx_attr (SOME c)= [("context", c)]
              | ctx_attr NONE    = []
	    fun xmlid x = XML.element "identifier" [] [XML.text x];
	in 
	    XML.element "idtable" (("objtype", ty)::ctx_attr ctx)
	                          (map xmlid ids)
	end
in

fun setids t = issue_pgip "setids" [] t
fun addids t = issue_pgip "addids" [] t
fun delids t = issue_pgip "delids" [] t

fun delallids ty = setids (xml_idtable ty NONE [])

fun send_thy_idtable ctx thys = setids (xml_idtable objtype_thy ctx thys)
fun clear_thy_idtable () = delallids objtype_thy

fun send_thm_idtable ctx thy = 
    addids (xml_idtable objtype_thm ctx (map fst (PureThy.thms_of thy)));

fun clear_thm_idtable () = delallids objtype_thm

(* fun send_type_idtable thy = TODO, it's a bit low-level messy
   & maybe not so useful anyway *)
	
end

(* Response to interface queries about known objects *)

local
 val topthy = Toplevel.theory_of o Toplevel.get_state
 fun pthm thy name = print_thm (get_thm thy (name, NONE))

 fun idvalue tp nm = ("idvalue",[("objtype",tp),("name",nm)])
in
fun askids (NONE, SOME "theory")  = send_thy_idtable NONE (ThyInfo.names())
  | askids (NONE, NONE) =  send_thy_idtable NONE (ThyInfo.names())
                           (* only theories known in top context *)
  | askids (SOME thy, SOME "theory") = send_thy_idtable (SOME thy) (ThyInfo.get_preds thy)
  | askids (SOME thy, SOME "theorem") = send_thm_idtable (SOME thy) (ThyInfo.get_theory thy)
  | askids (SOME thy, NONE) = (send_thy_idtable (SOME thy) (ThyInfo.get_preds thy);
                               send_thm_idtable (SOME thy) (ThyInfo.get_theory thy)) 
  | askids (_, SOME ot) = error ("No objects of type "^(quote ot)^" are available here.")

fun showid (_,        "theory", n) = 
    with_displaywrap (idvalue "theory" n) (fn ()=>(print_theory (ThyInfo.get_theory n)))
  | showid (SOME thy, "theorem", t) = 
    with_displaywrap (idvalue "theorem" t) (fn ()=>(pthm (ThyInfo.get_theory thy) t))
  | showid (NONE,     "theorem", t) = 
    with_displaywrap (idvalue "theorem" t) (fn ()=>pthm (topthy()) t)
  | showid (_, ot, _) = error ("Cannot show objects of type "^ot)

end


(** Parsing proof scripts without execution **)

(* Notes.
   This is quite tricky, because 1) we need to put back whitespace which
   was removed during parsing so we can provide markup around commands;
   2) parsing is intertwined with execution in Isar so we have to repeat
   the parsing to extract interesting parts of commands.  The trace of
   tokens parsed which is now recorded in each transition (added by
   Markus June '04) helps do this, but the mechanism is still a bad mess.
   
   If we had proper parse trees it would be easy, although having a
   fixed type of trees might be tricky with Isar's extensible parser.

   Probably the best solution is to produce the meta-information at
   the same time as the parse, for each command, e.g. by recording a 
   list of (name,objtype) pairs which record the bindings created by 
   a command.  This would require changing the interfaces in 
   outer_syntax.ML (or providing new ones), 

    datatype metainfo = Binding of string * string  (* name, objtype *)

    val command_withmetainfo: string -> string -> string ->
      (token list -> 
       ((Toplevel.transition -> Toplevel.transition) * metainfo list) * 
       token list) -> parser

   We'd also like to render terms as they appear in output, but this
   will be difficult because inner syntax is extensible and we don't 
   have the correct syntax to hand when just doing outer parsing
   without execution.  A reasonable approximation could 
   perhaps be obtained by using the syntax of the current context.
   However, this would mean more mess trying to pick out terms, 
   so at this stage a more serious change to the Isar functions
   seems necessary.
*)

local
    fun markup_text str elt = [XML.element elt [] [XML.text str]]
    fun markup_text_attrs str elt attrs = [XML.element elt attrs [XML.text str]]
    fun empty elt = [XML.element elt [] []]

    fun whitespace str = XML.element "whitespace" [] [XML.text str]

    (* an extra token is needed to terminate the command *)
    val sync = OuterSyntax.scan "\\<^sync>";

    fun named_item_elt_with nameattr toks str elt nameP objtyp = 
	let 
	    val name = (fst (nameP (toks@sync)))
			handle _ => (error ("Error occurred in name parser for " ^ elt ^ 
					    "(objtype: " ^ objtyp ^ ")");
				     "")
				     
	in 
	    [XML.element elt 
			 ((if name="" then [] else [(nameattr, name)])@
			  (if objtyp="" then [] else [("objtype", objtyp)]))
			 ([XML.text str])]
	end

    val named_item_elt = named_item_elt_with "name"
    val thmnamed_item_elt = named_item_elt_with "thmname"
 
    fun parse_warning msg = warning ("script->PGIP markup parser: " ^ msg)

    (* FIXME: allow dynamic extensions to language here: we need a hook in
       outer_syntax.ML to reset this table. *)

    val keywords_classification_table = ref (NONE:(string Symtab.table) option)

    fun get_keywords_classification_table () = 
	case (!keywords_classification_table) of
	    SOME t => t
	  | NONE => (let
			 val tab = Library.foldl (fn (tab,(c,_,k,_))=>Symtab.update((c,k),tab)) 
					 (Symtab.empty,OuterSyntax.dest_parsers())
		     in (keywords_classification_table := SOME tab; tab) end)


		    
    fun xmls_of_thy_begin (name,toks,str) = (* see isar_syn.ML/theoryP *)
	let 
	    val ((thyname,imports,files),_) = ThyHeader.args (toks@sync)
	in 
	    markup_text_attrs str "opentheory" 
			      ([("thyname",thyname)] @
			       (if imports=[] then [] else
				[("parentnames", (space_implode ";" imports) ^ ";")]))
	end 

    fun xmls_of_thy_decl (name,toks,str) = (* see isar_syn.ML/thy_decl cases  *)
	let 
	    (* NB: PGIP only deals with single name bindings at the moment;
	       potentially we could markup grouped definitions several times but
	       that might suggest the wrong structure to the editor?
	       Better alternative would be to put naming closer around text,
	       but to do that we'd be much better off modifying the real parser
	       instead of reconstructing it here. *)

	    val plain_items = (* no names, unimportant names, or too difficult *)
		["defaultsort","arities","judgement","finalconsts",
		 "syntax", "nonterminals", "translations",
		 "global", "local", "hide",
		 "ML_setup", "setup", "method_setup",
		 "parse_ast_translation", "parse_translation", "print_translation",
		 "typed_print_translation", "print_ast_translation", "token_translation",
		 "oracle",
		 "declare"]

	    val plain_item   = markup_text str "theoryitem"
	    val comment_item = markup_text str "litcomment"
	    val named_item   = named_item_elt toks str "theoryitem"

	    val opt_overloaded = P.opt_keyword "overloaded";

	    val thmnameP = (* see isar_syn.ML/theoremsP *)
		let 
		    val name_facts = P.and_list1 ((P.opt_thm_name "=" --| P.xthms1) >> #1)
		    val theoremsP = P.locale_target |-- name_facts >> (fn [] => "" | x::_ => x)
		in 
		    theoremsP
		end
	in 
	    (* TODO: ideally we would like to render terms properly, just as they are
	       in output. This implies using PGML for symbols and variables/atoms. 
	       BUT it's rather tricky without having the correct concrete syntax to hand,
	       and even if we did, we'd have to mess around here a whole lot more first
	       to pick out the terms from the syntax. *)

	    if (name mem plain_items) then plain_item
	    else case name of
		     "text"      => comment_item
		   | "text_raw"  => comment_item
		   | "typedecl"  => named_item (P.type_args |-- P.name) "type"
		   | "types"     => named_item (P.type_args |-- P.name) "type"
		   | "classes"   => named_item P.name "class"
		   | "classrel"  => named_item P.name "class"
		   | "consts"    => named_item (P.const >> #1) "term"
		   | "axioms"    => named_item (P.spec_name >> (#1 o #1)) "theorem"
		   | "defs"	 => named_item (opt_overloaded |-- P.spec_name >> (#1 o #1)) "theorem"
		   | "constdefs" => named_item P.name (* FIXME: heavily simplified/wrong *) "term"
		   | "theorems"  => named_item thmnameP "thmset"
		   | "lemmas"    => named_item thmnameP "thmset"
		   | "oracle"    => named_item P.name "oracle"
		   | "locale"	 => named_item ((P.opt_keyword "open" >> not) |-- P.name) "locale"
		   | _ => (parse_warning ("Unrecognized thy-decl command: " ^ name); plain_item)
	end

    fun xmls_of_thy_goal (name,toks,str) = 
	let 
	    (* see isar_syn.ML/gen_theorem *)
         val statement = P.and_list1 (P.opt_thm_name ":" -- Scan.repeat1 P.propp);
	 val general_statement =
	    statement >> pair [] || Scan.repeat P.locale_elem_or_expr -- (P.$$$ "shows" |-- statement);
	    
	    val gen_theoremP =
		P.locale_target -- Scan.optional (P.opt_thm_name ":" --|
                 Scan.ahead (P.locale_keyword || P.$$$ "shows")) ("", []) --
				 general_statement >> (fn ((locale, a), (elems, concl)) => 
							 fst a) (* a : (bstring * Args.src list) *)

	    val nameP = P.locale_target |-- (Scan.optional ((P.opt_thm_name ":") >> #1) "")
	    (* TODO: add preference values for attributes 
	       val prefval = XML.element "prefval" [("name","thm-kind"),("value",name)]
	    *)
	in 
	    (thmnamed_item_elt toks str "opengoal" nameP "") @
	    (empty "openblock")
	end

    fun xmls_of_qed (name,markup) = (case name of
      "sorry"         => markup "giveupgoal"
    | "oops"          => markup "postponegoal"
    | "done"          => markup "closegoal" 
    | "by"            => markup "closegoal"  (* nested or toplevel *)
    | "qed"           => markup "closegoal"  (* nested or toplevel *)
    | "."	      => markup "closegoal"  (* nested or toplevel *)
    | ".."	      => markup "closegoal"  (* nested or toplevel *)
    | other => (* default to closegoal: may be wrong for extns *)
      (parse_warning ("Unrecognized qed command: " ^ quote other); markup "closegoal"))
	@ (empty "closeblock")

    fun xmls_of_kind kind (name,toks,str) = 
    let val markup = markup_text str in 
    case kind of
      "control"        => markup "badcmd"
    | "diag"           => markup "spuriouscmd"
    (* theory/files *)
    | "theory-begin"   => xmls_of_thy_begin (name,toks,str)
    | "theory-decl"    => xmls_of_thy_decl (name,toks,str)
    | "theory-heading" => markup "litcomment"
    | "theory-script"  => markup "theorystep"
    | "theory-end"     => markup "closetheory"
    (* proof control *)
    | "theory-goal"    => xmls_of_thy_goal (name,toks,str)
    | "proof-heading"  => markup "litcomment"
    | "proof-goal"     => (markup "opengoal") @ (empty "openblock")  (* nested proof: have, show, ... *)
    | "proof-block"    => markup "proofstep" (* context-shift: proof, next; really "newgoal" *)
    | "proof-open"     => (empty "openblock") @ (markup "proofstep")
    | "proof-close"    => (markup "proofstep") @ (empty "closeblock")
    | "proof-script"   => markup "proofstep"
    | "proof-chain"    => markup "proofstep"
    | "proof-decl"     => markup "proofstep"
    | "proof-asm"      => markup "proofstep"
    | "proof-asm-goal" => (markup "opengoal") @ (empty "openblock")  (* nested proof: obtain *)
    | "qed"            => xmls_of_qed (name,markup)
    | "qed-block"      => xmls_of_qed (name,markup)
    | "qed-global"     => xmls_of_qed (name,markup)
    | other => (* default for anything else is "spuriouscmd", ignored for undo. *)
      (parse_warning ("Internal inconsistency, unrecognized keyword kind: " ^ quote other);
       markup "spuriouscmd")
    end 
in

fun xmls_of_transition (name,str,toks) = 
   let 
       val classification = Symtab.lookup (get_keywords_classification_table(),name)
   in case classification of
	  SOME k => (xmls_of_kind k (name,toks,str))
	| NONE => (* an uncategorized keyword ignored for undo (maybe wrong) *)
	  (parse_warning ("Uncategorized command found: " ^ name ^ " -- using spuriouscmd");
	   markup_text str "spuriouscmd")
   end 

fun markup_toks [] _ = []
  | markup_toks toks x = markup_text (implode (map OuterLex.unparse toks)) x

fun markup_comment_whs [] = []
  | markup_comment_whs (toks as t::ts) = (* this would be a lot neater if we could match on toks! *)
    let 
	val (prewhs,rest) = take_prefix (OuterLex.is_kind OuterLex.Space) toks
    in 
	if (prewhs <> []) then
	    whitespace (implode (map OuterLex.unparse prewhs))
	    :: (markup_comment_whs rest)
	else 
	    (markup_text (OuterLex.unparse t) "comment") @
	    (markup_comment_whs ts)
    end

fun xmls_pre_cmd [] = ([],[])
  | xmls_pre_cmd toks = 
    let 
	(* an extra token is needed to terminate the command *)
	val sync = OuterSyntax.scan "\\<^sync>";
	val spaceP = Scan.bulk (Scan.one (fn t =>not (OuterLex.is_proper t)) >> OuterLex.val_of) >> implode
	val text_with_whs = 
	    ((spaceP || Scan.succeed "") --
	     (P.short_ident || P.long_ident || P.sym_ident || P.string || P.number || P.verbatim) >> op^)
	     -- (spaceP || Scan.succeed "") >> op^
	val (prewhs,rest1) = take_prefix (not o OuterLex.is_proper) (toks@sync)
        (* NB: this collapses  litcomment,(litcomment|whitespace)* to a single litcomment *)
	val (_,rest2) = (Scan.bulk (P.$$$ "--" |-- text_with_whs) >> implode || Scan.succeed "") rest1
	val (postwhs,rest3) = take_prefix (not o OuterLex.is_proper) rest2
    in
	((markup_comment_whs prewhs) @
	 (if (length rest2 = length rest1)  then []
	  else markup_text (implode 
				(map OuterLex.unparse (Library.take (length rest1 - length rest2, rest1))))
	       "litcomment") @
	 (markup_comment_whs postwhs),
	 Library.take (length rest3 - 1,rest3))
    end

fun xmls_of_impropers toks = 
    let 
	val (xmls,rest) = xmls_pre_cmd toks
    in
	xmls @ (markup_toks rest "unparseable")
    end
    
fun markup_unparseable str = markup_text str "unparseable" 

end


local
    (* we have to weave together the whitespace/comments with proper tokens 
       to reconstruct the input. *)
    (* TODO: see if duplicating isar_output/parse_thy can help here *)

    fun match_tokens ([],us,vs) = (rev vs,us)  (* used, unused *)
      | match_tokens (t::ts,w::ws,vs) = 
	if (t = w) then match_tokens (ts,ws,w::vs)
	else match_tokens (t::ts,ws,w::vs)
      | match_tokens _ = error ("match_tokens: mismatched input parse")
in
    fun xmls_of_str str =
    let 
       (* parsing:   See outer_syntax.ML/toplevel_source *)
       fun parse_loop ([],[],xmls) = xmls
	 | parse_loop ([],itoks,xmls) = xmls @ (xmls_of_impropers itoks)
	 | parse_loop ((nm,toks,_)::trs,itoks,xmls) =
	   let 
	       (* first proper token after whitespace/litcomment/whitespace is command *)
	       val (xmls_pre_cmd,cmditoks') = xmls_pre_cmd itoks
	       val (cmdtok,itoks') = case cmditoks' of x::xs => (x,xs)
						     | _ => error("proof_general/parse_loop impossible") 
	       val (utoks,itoks'') = match_tokens (toks,itoks',[])
               (* FIXME: should take trailing [w/s+] semicolon too in utoks *)

	       val str = implode (map OuterLex.unparse (cmdtok::utoks))

	       val xmls_tr  = xmls_of_transition (nm,str,toks)
	   in
	       parse_loop(trs,itoks'',xmls @ xmls_pre_cmd @ xmls_tr)
	   end
    in
	(let val toks = OuterSyntax.scan str
	 in 
	     parse_loop (OuterSyntax.read toks, toks, [])
	 end)
 	   handle _ => markup_unparseable str
    end


fun parsescript str attrs = 
    let 
	val _ = start_delay_msgs ()  (* accumulate error messages to put inside parseresult *)
	val xmls = xmls_of_str str
        val errs = end_delayed_msgs ()
     in 
	issue_pgips [XML.element "parseresult" attrs (errs@xmls)]
    end
end



(**** PGIP:  Isabelle -> Interface ****)

val isabelle_pgml_version_supported = "1.0"; 
val isabelle_pgip_version_supported = "2.0"

fun usespgip () = 
    issue_pgipe "usespgip" [("version", isabelle_pgip_version_supported)];

fun usespgml () = 
    issue_pgipe "usespgml" [("version", isabelle_pgml_version_supported)];

fun hasprefs () = 
    List.app (fn (prefcat, prefs) =>
	    issue_pgips [XML.element "hasprefs" [("prefcategory",prefcat)]
		 (map (fn (name, (descr, (ty, (default,_),_))) => 
		       XML.element "haspref" [("name", name), 
					      ("descr", descr), 
					      ("default", default)]
		       [ty]) prefs)]) (!preferences);

fun allprefs () = Library.foldl (op @) ([], map snd (!preferences))
	
fun setpref name value = 
    (case assoc (allprefs(), name) of
	 NONE => warning ("Unknown pref: " ^ quote name)
       | SOME (_, (_, _, set)) => set value);

fun getpref name = 
    (case assoc (allprefs(), name) of
	 NONE => warning ("Unknown pref: " ^ quote name)
       | SOME (_, (_, (_,get), _)) => 
	   issue_pgip "prefval" [("name", name)] (get ()));





(**** PGIP:  Interface -> Isabelle/Isar ****)

exception PGIP of string;
exception PGIP_QUIT;

(* Sending commands to Isar *)

fun isarcmd s = 
    s |> OuterSyntax.scan |> OuterSyntax.read 
      |> map (Toplevel.position (Position.name "PGIP message") o #3) |> Toplevel.>>>;

fun xmltext ((XML.Text text)::ts) = text ^ (xmltext ts)
  | xmltext [] = ""
  | xmltext _ = raise PGIP "Expected text (PCDATA/CDATA)"
			
fun isarscript xmls = isarcmd (xmltext xmls)   (* send a script command *)


(* load an arbitrary (.thy or .ML) file *)

fun use_thy_or_ml_file file = 
    let
	val (path,extn) = Path.split_ext (Path.unpack file)
    in
	case extn of
	    "" => isarcmd ("use_thy " ^ (quote (Path.pack path)))
	  | "thy" => isarcmd ("use_thy " ^ (quote (Path.pack path)))
	  | "ML" => isarcmd ("use " ^ quote file)
	  (* instead of error we could guess theory? *)
	  | other => error ("Don't know how to read a file with extension " ^ other)
    end  


(* Proof control commands *)

local
  fun xmlattro attr attrs = assoc(attrs,attr)

  fun xmlattr attr attrs = 
      (case xmlattro attr attrs of 
	   SOME value => value 
	 | NONE => raise PGIP ("Missing attribute: " ^ attr))

  val thyname_attro = xmlattro "thyname"
  val thyname_attr = xmlattr "thyname"
  val objtype_attro = xmlattro "objtype"
  val objtype_attr = xmlattr "objtype"
  val name_attr = xmlattr "name"
  val dirname_attr = xmlattr "dir"
  fun comment x = "(* " ^ x ^ " *)"

  fun localfile_of_url url = (* only handle file:/// or file://localhost/ *)
      case Url.unpack url of
	  (Url.File path) => File.sysify_path path
	| _ => error ("Cannot access non-local URL " ^ url)

  val fileurl_of = localfile_of_url o (xmlattr "url")

  val topthy = Toplevel.theory_of o Toplevel.get_state
  val topthy_name = PureThy.get_name o topthy

  val currently_open_file = ref (NONE : string option)

  val topnode = Toplevel.node_of o Toplevel.get_state
  fun topproofpos () = (case topnode() of Toplevel.Proof ph => SOME(ProofHistory.position ph) 
					| _ => NONE) handle Toplevel.UNDEF => NONE
in

fun process_pgip_element pgipxml = (case pgipxml of 
  (XML.Text t) => warning ("Ignored text in PGIP packet: \n" ^ t)
| (xml as (XML.Elem (elem, attrs, data))) => 
  (case elem of
       (* protocol config *)
       "askpgip"  	=> (pgip_emacs_compatibility_flag:=false; (* PG>=3.6 or other PGIP interface *)
			    usespgip (); send_pgip_config ())
     | "askpgml"  	=> usespgml ()
     (* proverconfig *)
     | "askprefs" 	=> hasprefs ()
     | "getpref"  	=> getpref (name_attr attrs)
     | "setpref"  	=> setpref (name_attr attrs) (xmltext data)
     (* provercontrol *)
     | "proverinit" 	=> isar_restart ()
     | "proverexit" 	=> isarcmd "quit"
     | "startquiet" 	=> isarcmd "disable_pr"
     | "stopquiet"  	=> isarcmd "enable_pr"
     | "pgmlsymbolson"   => (print_mode := !print_mode @ ["xsymbols"])
     | "pgmlsymbolsoff"  => (print_mode := (!print_mode \ "xsymbols"))
     (* properproofcmd: proper commands which belong in script *)
     (* FIXME: next ten are by Eclipse interface, can be removed in favour of dostep *)
     (* NB: Isar doesn't make lemma name of goal accessible during proof *)
     | "opengoal"       => isarscript data
     | "proofstep"      => isarscript data
     | "closegoal"      => isarscript data
     | "giveupgoal"     => isarscript data
     | "postponegoal"   => isarscript data
     | "comment"	=> isarscript data  (* NB: should be ignored, but process anyway *)
     | "whitespace"	=> isarscript data  (* NB: should be ignored, but process anyway *)
     | "litcomment"     => isarscript data
     | "spuriouscmd"    => isarscript data
     | "badcmd"		=> isarscript data
     (* improperproofcmd: improper commands which *do not* belong in script *)
     | "dostep"         => isarscript data
     | "undostep"       => isarcmd "undos_proof 1"
     | "redostep"       => isarcmd "redo"
     | "abortgoal"      => isarcmd "ProofGeneral.kill_proof"  
     | "forget"         => error "Not implemented" 
     | "restoregoal"    => error "Not implemented"  (* could just treat as forget? *)
     (* proofctxt: improper commands *)
     | "askids"         => askids (thyname_attro attrs, objtype_attro attrs)
     | "showid"	        => showid (thyname_attro attrs, objtype_attr attrs, name_attr attrs)
     | "askguise"	=> send_informguise (!currently_open_file,
					     SOME (topthy_name()) handle Toplevel.UNDEF => NONE,
					     topproofpos())
     | "parsescript"    => parsescript (xmltext data) attrs (* just pass back attrs unchanged *)
     | "showproofstate" => isarcmd "pr"
     | "showctxt"       => isarcmd "print_theory"   (* more useful than print_context *)
     | "searchtheorems" => isarcmd ("thms_containing " ^ (xmltext data))
     | "setlinewidth"   => isarcmd ("pretty_setmargin " ^ (xmltext data))
     | "viewdoc"	=> isarcmd ("print_" ^ (xmltext data)) (* FIXME: isatool doc? *)
     (* properfilecmd: proper theory-level script commands *)
     (* FIXME: next three are sent by Eclipse, can be removed in favour of doitem *)
     | "opentheory"     => isarscript data
     | "theoryitem"     => isarscript data
     | "closetheory"    => let 
			      val thyname = topthy_name()
			   in (isarscript data;
			       writeln ("Theory \""^thyname^"\" completed."))
			   end
     (* improperfilecmd: theory-level commands not in script *)
     | "doitem"         => isarscript data
     | "undoitem"       => isarcmd "ProofGeneral.undo"
     | "redoitem"       => isarcmd "ProofGeneral.redo"
     | "aborttheory"    => isarcmd ("init_toplevel")
     | "retracttheory"  => isarcmd ("kill_thy " ^ (quote (thyname_attr attrs)))
     | "loadfile"       => use_thy_or_ml_file (fileurl_of attrs)
     | "openfile"       => (openfile_retract (fileurl_of attrs);
			    currently_open_file := SOME (fileurl_of attrs))
     | "closefile"      => (case !currently_open_file of 
				SOME f => (inform_file_processed f;
					   currently_open_file := NONE)
			      | NONE => raise PGIP ("closefile when no file is open!"))
     | "abortfile"      => (currently_open_file := NONE) (* perhaps error for no file open *)
     | "changecwd"      => ThyLoad.add_path (dirname_attr attrs)
     | "systemcmd"	=> isarscript data
     (* unofficial command for debugging *)
     | "quitpgip" => raise PGIP_QUIT  
     | _ => raise PGIP ("Unrecognized PGIP element: " ^ elem)))

fun process_pgip_tree xml = 
    (pgip_refseq := NONE; 
     pgip_refid := NONE;
     (case xml of
	  XML.Elem ("pgip", attrs, pgips) => 
	  (let 
	       val class = xmlattr "class" attrs
	       val _ = (pgip_refseq := xmlattro "seq" attrs;
			pgip_refid :=  xmlattro "id" attrs)
	   in  
	       if (class = "pa") then
		   List.app process_pgip_element pgips
	       else 
		   raise PGIP "PGIP packet for me should have class=\"pa\""
	   end)
	| _ => raise PGIP "Invalid PGIP packet received") 
     handle (PGIP msg) => 
	    (error (msg ^ "\nPGIP error occured in XML text below:\n" ^ 
		    (XML.string_of_tree xml))))

val process_pgip = process_pgip_tree o XML.tree_of_string;

end




(* PGIP loop: process PGIP input only *)

local  

exception XML_PARSE

fun loop src : unit =
    let 
	val _ = issue_pgipe "ready" []
	val pgipo = (Source.get_single src) 
			handle e => raise XML_PARSE
    in
	case pgipo of  
	     NONE  => ()
	   | SOME (pgip,src') =>  
	     ((process_pgip_tree pgip); loop src') handle e => handler (e,SOME src')
    end handle e => handler (e,SOME src)  (* i.e. error in ready/XML parse *)

and handler (e,srco) = 
    case (e,srco) of
        (XML_PARSE,SOME src) => 
	(* (!error_fn "Invalid XML input, aborting"; ()) NB: loop OK for tty, but want exit on EOF *)
         panic "Invalid XML input, aborting"
      | (PGIP_QUIT,_) => ()
      | (ERROR,SOME src) => loop src (* message already given *)
      | (Interrupt,SOME src) => (!error_fn "Interrupt during PGIP processing"; loop src)
      | (Toplevel.UNDEF,SOME src) => (error "No working context defined"; loop src) (* usually *)
      | (e,SOME src) => (error (Toplevel.exn_message e); loop src)
      | (_,NONE) => ()
in
  (* NB: simple tty for now; later might read from other tty-style sources (e.g. sockets). *)

  val xmlP = XML.scan_comment_whspc |-- XML.parse_elem >> single

  val tty_src = Source.set_prompt "" (Source.source Symbol.stopper xmlP NONE Source.tty)

  val pgip_toplevel =  loop 
end


(* additional outer syntax for Isar *)

local structure P = OuterParse and K = OuterSyntax.Keyword in

val undoP = (* undo without output *)
  OuterSyntax.improper_command "ProofGeneral.undo" "(internal)" K.control
    (Scan.succeed (Toplevel.no_timing o IsarCmd.undo));

val redoP = (* redo without output *)
  OuterSyntax.improper_command "ProofGeneral.redo" "(internal)" K.control
    (Scan.succeed (Toplevel.no_timing o IsarCmd.redo));

val context_thy_onlyP =
  OuterSyntax.improper_command "ProofGeneral.context_thy_only" "(internal)" K.control
    (P.name >> (Toplevel.no_timing oo IsarThy.init_context update_thy_only));

val try_context_thy_onlyP =
  OuterSyntax.improper_command "ProofGeneral.try_context_thy_only" "(internal)" K.control
    (P.name >> (Toplevel.no_timing oo
      (Toplevel.imperative (K ()) oo IsarThy.init_context try_update_thy_only)));

val restartP =
  OuterSyntax.improper_command "ProofGeneral.restart" "(internal)" K.control
    (P.opt_unit >> (Toplevel.no_timing oo K (Toplevel.imperative isar_restart)));

val kill_proofP =
  OuterSyntax.improper_command "ProofGeneral.kill_proof" "(internal)" K.control
    (Scan.succeed (Toplevel.no_timing o IsarCmd.kill_proof_notify tell_clear_goals));

val inform_file_processedP =
  OuterSyntax.improper_command "ProofGeneral.inform_file_processed" "(internal)" K.control
    (P.name >> (fn file => Toplevel.no_timing o
      Toplevel.keep (vacuous_inform_file_processed file) o
      Toplevel.kill o
      Toplevel.keep (proper_inform_file_processed file)));

val inform_file_retractedP =
  OuterSyntax.improper_command "ProofGeneral.inform_file_retracted" "(internal)" K.control
    (P.name >> (Toplevel.no_timing oo
      (fn file => Toplevel.imperative (fn () => inform_file_retracted file))));

val process_pgipP =
  OuterSyntax.improper_command "ProofGeneral.process_pgip" "(internal)" K.control
    (P.text >> (Toplevel.no_timing oo
      (fn txt => Toplevel.imperative (fn () => process_pgip txt))));

fun init_outer_syntax () = OuterSyntax.add_parsers
 [undoP, redoP, restartP, kill_proofP, context_thy_onlyP, try_context_thy_onlyP,
  inform_file_processedP, inform_file_retractedP, process_pgipP];

end;


(* init *)

val initialized = ref false;

fun set_prompts true _ = ml_prompts "ML> " "ML# " 
  | set_prompts false true = ml_prompts "PGIP-ML>" "PGIP-ML# "
  | set_prompts false false = 
    ml_prompts ("> " ^ oct_char "372") ("- " ^ oct_char "373");

fun init_setup isar pgip =
 (conditional (not (! initialized)) (fn () =>
   (if isar then setmp warning_fn (K ()) init_outer_syntax () else ();
    setup_xsymbols_output ();
    setup_pgml_output ();
    setup_messages ();
    setup_state ();
    setup_thy_loader ();
    setup_present_hook ();
    set initialized; ()));
  sync_thy_loader ();
  print_mode := proof_generalN :: (! print_mode \ proof_generalN);
  if pgip then 
      print_mode := pgmlN :: (pgmlatomsN :: (! print_mode \ pgmlN \ pgmlatomsN)) 
    else 
	pgip_emacs_compatibility_flag := true;  (* assume this for PG <3.6 compatibility *)
  set quick_and_dirty;
  ThmDeps.enable ();
  set_prompts isar pgip;
  pgip_active := pgip)

fun init isar = 
    (init_setup isar false;
     if isar then ((* da: this welcome is problematic: clashes with welcome
		      issued for PG anyway. 
		      welcome (); *)
		    Isar.sync_main ()) else isa_restart ());

fun init_pgip false = panic "Sorry, PGIP not supported for Isabelle/classic mode."
  | init_pgip true = (init_setup true true; 
		      pgip_toplevel tty_src);



(** generate keyword classification elisp file **)

local

val regexp_meta = explode ".*+?[]^$";
val regexp_quote = 
    implode o map  (fn c => if c mem regexp_meta then "\\\\" ^ c else c)
    o explode;

fun defconst name strs =
  "\n(defconst isar-keywords-" ^ name ^
  "\n  '(" ^ space_implode "\n    " (map (quote o regexp_quote) strs) ^ "))\n";

fun make_elisp_commands commands kind =
  defconst kind (List.mapPartial 
		     (fn (c, _, k, _) => if k = kind then SOME c else NONE) 
		     commands);

fun make_elisp_syntax (keywords, commands) =
  ";;\n\
  \;; Keyword classification tables for Isabelle/Isar.\n\
  \;; This file was generated by " ^ Session.name () ^ " -- DO NOT EDIT!\n\
  \;;\n\
  \;; $" ^ "Id$\n\
  \;;\n" ^
  defconst "major" (map #1 commands) ^
  defconst "minor" (List.filter Syntax.is_identifier keywords) ^
  implode (map (make_elisp_commands commands) OuterSyntax.Keyword.kinds) ^
  "\n(provide 'isar-keywords)\n";

in

fun write_keywords s =
  (init_outer_syntax ();
    File.write (Path.unpack ("isar-keywords" ^ (if s = "" then "" else "-" ^ s) ^".el"))
      (make_elisp_syntax (OuterSyntax.dest_keywords (), 
			  OuterSyntax.dest_parsers ())));

end;

end