src/Pure/Isar/outer_syntax.ML
author wenzelm
Tue May 20 20:05:43 2014 +0200 (2014-05-20)
changeset 57026 90a3e39be0ca
parent 56938 ef44b488bad8
child 57105 bf5ddf4ec64b
permissions -rw-r--r--
afford strict check (see also AFP/a8e08d947f0a);
     1 (*  Title:      Pure/Isar/outer_syntax.ML
     2     Author:     Markus Wenzel, TU Muenchen
     3 
     4 The global Isabelle/Isar outer syntax.
     5 
     6 Note: the syntax for files is statically determined at the very
     7 beginning; for interactive processing it may change dynamically.
     8 *)
     9 
    10 signature OUTER_SYNTAX =
    11 sig
    12   type outer_syntax
    13   val batch_mode: bool Unsynchronized.ref
    14   val is_markup: outer_syntax -> Thy_Output.markup -> string -> bool
    15   val get_syntax: unit -> (Scan.lexicon * Scan.lexicon) * outer_syntax
    16   val check_syntax: unit -> unit
    17   type command_spec = (string * Keyword.T) * Position.T
    18   val command: command_spec -> string ->
    19     (Toplevel.transition -> Toplevel.transition) parser -> unit
    20   val markup_command: Thy_Output.markup -> command_spec -> string ->
    21     (Toplevel.transition -> Toplevel.transition) parser -> unit
    22   val improper_command: command_spec -> string ->
    23     (Toplevel.transition -> Toplevel.transition) parser -> unit
    24   val local_theory': command_spec -> string ->
    25     (bool -> local_theory -> local_theory) parser -> unit
    26   val local_theory: command_spec -> string ->
    27     (local_theory -> local_theory) parser -> unit
    28   val local_theory_to_proof': command_spec -> string ->
    29     (bool -> local_theory -> Proof.state) parser -> unit
    30   val local_theory_to_proof: command_spec -> string ->
    31     (local_theory -> Proof.state) parser -> unit
    32   val help_outer_syntax: string list -> unit
    33   val print_outer_syntax: unit -> unit
    34   val scan: Position.T -> string -> Token.T list
    35   val parse: Position.T -> string -> Toplevel.transition list
    36   type isar
    37   val isar: TextIO.instream -> bool -> isar
    38   val side_comments: Token.T list -> Token.T list
    39   val command_reports: outer_syntax -> Token.T -> (Position.report * string) list
    40   val read_spans: outer_syntax -> Token.T list -> Toplevel.transition list
    41 end;
    42 
    43 structure Outer_Syntax: OUTER_SYNTAX =
    44 struct
    45 
    46 (** outer syntax **)
    47 
    48 (* command parsers *)
    49 
    50 datatype command = Command of
    51  {comment: string,
    52   markup: Thy_Output.markup option,
    53   int_only: bool,
    54   parse: (Toplevel.transition -> Toplevel.transition) parser,
    55   pos: Position.T,
    56   id: serial};
    57 
    58 fun new_command comment markup int_only parse pos =
    59   Command {comment = comment, markup = markup, int_only = int_only, parse = parse,
    60     pos = pos, id = serial ()};
    61 
    62 fun command_markup def (name, Command {pos, id, ...}) =
    63   Markup.properties (Position.entity_properties_of def id pos)
    64     (Markup.entity Markup.commandN name);
    65 
    66 fun pretty_command (cmd as (name, Command {comment, ...})) =
    67   Pretty.block
    68     (Pretty.marks_str
    69       ([Active.make_markup Markup.sendbackN {implicit = true, properties = [Markup.padding_line]},
    70         command_markup false cmd], name) :: Pretty.str ":" :: Pretty.brk 2 :: Pretty.text comment);
    71 
    72 
    73 (* parse command *)
    74 
    75 local
    76 
    77 fun terminate false = Scan.succeed ()
    78   | terminate true =
    79       Parse.group (fn () => "end of input")
    80         (Scan.option Parse.sync -- Parse.semicolon >> K ());
    81 
    82 fun body cmd (name, _) =
    83   (case cmd name of
    84     SOME (Command {int_only, parse, ...}) =>
    85       Parse.!!! (Scan.prompt (name ^ "# ") (Parse.tags |-- parse >> pair int_only))
    86   | NONE =>
    87       Scan.succeed (false, Toplevel.imperative (fn () =>
    88         error ("Bad parser for outer syntax command " ^ quote name))));
    89 
    90 in
    91 
    92 fun parse_command do_terminate cmd =
    93   Parse.semicolon >> K NONE ||
    94   Parse.sync >> K NONE ||
    95   (Parse.position Parse.command :-- body cmd) --| terminate do_terminate
    96     >> (fn ((name, pos), (int_only, f)) =>
    97       SOME (Toplevel.empty |> Toplevel.name name |> Toplevel.position pos |>
    98         Toplevel.interactive int_only |> f));
    99 
   100 end;
   101 
   102 
   103 (* type outer_syntax *)
   104 
   105 datatype outer_syntax = Outer_Syntax of
   106  {commands: command Symtab.table,
   107   markups: (string * Thy_Output.markup) list};
   108 
   109 fun make_outer_syntax commands markups =
   110   Outer_Syntax {commands = commands, markups = markups};
   111 
   112 val empty_outer_syntax = make_outer_syntax Symtab.empty [];
   113 
   114 
   115 fun map_commands f (Outer_Syntax {commands, ...}) =
   116   let
   117     val commands' = f commands;
   118     val markups' =
   119       Symtab.fold (fn (name, Command {markup = SOME m, ...}) => cons (name, m) | _ => I)
   120         commands' [];
   121   in make_outer_syntax commands' markups' end;
   122 
   123 fun dest_commands (Outer_Syntax {commands, ...}) =
   124   commands |> Symtab.dest |> sort_wrt #1;
   125 
   126 fun lookup_commands (Outer_Syntax {commands, ...}) = Symtab.lookup commands;
   127 
   128 fun is_markup (Outer_Syntax {markups, ...}) kind name =
   129   AList.lookup (op =) markups name = SOME kind;
   130 
   131 
   132 
   133 (** global outer syntax **)
   134 
   135 type command_spec = (string * Keyword.T) * Position.T;
   136 
   137 val batch_mode = Unsynchronized.ref false;
   138 
   139 local
   140 
   141 (*synchronized wrt. Keywords*)
   142 val global_outer_syntax = Unsynchronized.ref empty_outer_syntax;
   143 
   144 fun add_command (name, kind) cmd = CRITICAL (fn () =>
   145   let
   146     val context = ML_Context.the_generic_context ();
   147     val thy = Context.theory_of context;
   148     val Command {pos, ...} = cmd;
   149     val _ =
   150       (case try (Thy_Header.the_keyword thy) name of
   151         SOME spec =>
   152           if Option.map #1 spec = SOME (Keyword.kind_files_of kind) then ()
   153           else error ("Inconsistent outer syntax keyword declaration " ^
   154             quote name ^ Position.here pos)
   155       | NONE =>
   156           if Context.theory_name thy = Context.PureN
   157           then Keyword.define (name, SOME kind)
   158           else error ("Undeclared outer syntax command " ^ quote name ^ Position.here pos));
   159     val _ = Context_Position.report_generic context pos (command_markup true (name, cmd));
   160   in
   161     Unsynchronized.change global_outer_syntax (map_commands (fn commands =>
   162      (if not (Symtab.defined commands name) then ()
   163       else if ! batch_mode then
   164         error ("Attempt to redefine outer syntax command " ^ quote name)
   165       else
   166         warning ("Redefining outer syntax command " ^ quote name ^
   167           Position.here (Position.thread_data ()));
   168       Symtab.update (name, cmd) commands)))
   169   end);
   170 
   171 in
   172 
   173 fun get_syntax () = CRITICAL (fn () => (Keyword.get_lexicons (), ! global_outer_syntax));
   174 
   175 fun check_syntax () =
   176   let
   177     val ((_, major), syntax) = CRITICAL (fn () => (Keyword.dest (), ! global_outer_syntax));
   178   in
   179     (case subtract (op =) (map #1 (dest_commands syntax)) major of
   180       [] => ()
   181     | missing => error ("Missing outer syntax command(s) " ^ commas_quote missing))
   182   end;
   183 
   184 fun lookup_commands_dynamic () = lookup_commands (! global_outer_syntax);
   185 
   186 fun command (spec, pos) comment parse =
   187   add_command spec (new_command comment NONE false parse pos);
   188 
   189 fun markup_command markup (spec, pos) comment parse =
   190   add_command spec (new_command comment (SOME markup) false parse pos);
   191 
   192 fun improper_command (spec, pos) comment parse =
   193   add_command spec (new_command comment NONE true parse pos);
   194 
   195 end;
   196 
   197 
   198 (* local_theory commands *)
   199 
   200 fun local_theory_command trans command_spec comment parse =
   201   command command_spec comment (Parse.opt_target -- parse >> (fn (loc, f) => trans loc f));
   202 
   203 val local_theory' = local_theory_command Toplevel.local_theory';
   204 val local_theory = local_theory_command Toplevel.local_theory;
   205 val local_theory_to_proof' = local_theory_command Toplevel.local_theory_to_proof';
   206 val local_theory_to_proof = local_theory_command Toplevel.local_theory_to_proof;
   207 
   208 
   209 (* inspect syntax *)
   210 
   211 fun help_outer_syntax pats =
   212   dest_commands (#2 (get_syntax ()))
   213   |> filter (fn (name, _) => forall (fn pat => match_string pat name) pats)
   214   |> map pretty_command
   215   |> Pretty.writeln_chunks;
   216 
   217 fun print_outer_syntax () =
   218   let
   219     val ((keywords, _), outer_syntax) =
   220       CRITICAL (fn () => (Keyword.dest (), #2 (get_syntax ())));
   221     val (int_cmds, cmds) =
   222       List.partition (fn (_, Command {int_only, ...}) => int_only) (dest_commands outer_syntax);
   223   in
   224     [Pretty.strs ("syntax keywords:" :: map quote keywords),
   225       Pretty.big_list "commands:" (map pretty_command cmds),
   226       Pretty.big_list "interactive-only commands:" (map pretty_command int_cmds)]
   227     |> Pretty.writeln_chunks
   228   end;
   229 
   230 
   231 
   232 (** toplevel parsing **)
   233 
   234 (* basic sources *)
   235 
   236 fun toplevel_source term do_recover cmd src =
   237   let
   238     val no_terminator =
   239       Scan.unless Parse.semicolon (Scan.one (Token.not_sync andf Token.not_eof));
   240     fun recover int =
   241       (int, fn _ => Scan.prompt "recover# " (Scan.repeat no_terminator) >> K [NONE]);
   242   in
   243     src
   244     |> Token.source_proper
   245     |> Source.source Token.stopper
   246       (Scan.bulk (Parse.$$$ "--" -- Parse.!!! Parse.document_source >> K NONE || Parse.not_eof >> SOME))
   247         (Option.map recover do_recover)
   248     |> Source.map_filter I
   249     |> Source.source Token.stopper
   250         (Scan.bulk (fn xs => Parse.!!! (parse_command term (cmd ())) xs))
   251         (Option.map recover do_recover)
   252     |> Source.map_filter I
   253   end;
   254 
   255 
   256 (* off-line scanning/parsing *)
   257 
   258 fun scan pos str =
   259   Source.of_string str
   260   |> Symbol.source
   261   |> Token.source {do_recover = SOME false} Keyword.get_lexicons pos
   262   |> Source.exhaust;
   263 
   264 fun parse pos str =
   265   Source.of_string str
   266   |> Symbol.source
   267   |> Token.source {do_recover = SOME false} Keyword.get_lexicons pos
   268   |> toplevel_source false NONE lookup_commands_dynamic
   269   |> Source.exhaust;
   270 
   271 
   272 (* interactive source of toplevel transformers *)
   273 
   274 type isar =
   275   (Toplevel.transition, (Toplevel.transition option,
   276     (Token.T, (Token.T option, (Token.T, (Token.T,
   277       (Symbol_Pos.T,
   278         Position.T * (Symbol.symbol, (Symbol.symbol, (string, unit) Source.source) Source.source)
   279   Source.source) Source.source) Source.source) Source.source)
   280   Source.source) Source.source) Source.source) Source.source;
   281 
   282 fun isar in_stream term : isar =
   283   Source.tty in_stream
   284   |> Symbol.source
   285   |> Source.map_filter (fn "\<^newline>" => SOME "\n" | s => SOME s)  (*Proof General legacy*)
   286   |> Token.source {do_recover = SOME true} Keyword.get_lexicons Position.none
   287   |> toplevel_source term (SOME true) lookup_commands_dynamic;
   288 
   289 
   290 (* side-comments *)
   291 
   292 fun cmts (t1 :: t2 :: toks) =
   293       if Token.keyword_with (fn s => s = "--") t1 then t2 :: cmts toks
   294       else cmts (t2 :: toks)
   295   | cmts _ = [];
   296 
   297 val side_comments = filter Token.is_proper #> cmts;
   298 
   299 
   300 (* read commands *)
   301 
   302 fun command_reports outer_syntax tok =
   303   if Token.is_command tok then
   304     let val name = Token.content_of tok in
   305       (case lookup_commands outer_syntax name of
   306         NONE => []
   307       | SOME cmd => [((Token.pos_of tok, command_markup false (name, cmd)), "")])
   308     end
   309   else [];
   310 
   311 fun read_spans outer_syntax toks =
   312   Source.of_list toks
   313   |> toplevel_source false NONE (K (lookup_commands outer_syntax))
   314   |> Source.exhaust;
   315 
   316 end;
   317