1 (* Title: Pure/Isar/isar_document.ML
4 Interactive Isar documents.
7 structure Isar_Document: sig end =
10 (* unique identifiers *)
12 type state_id = string;
13 type command_id = string;
14 type document_id = string;
19 val id_count = Synchronized.var "id" 0;
22 Synchronized.change_result id_count
25 in ("i" ^ string_of_int i', i') end);
28 fun err_dup kind id = error ("Duplicate " ^ kind ^ ": " ^ quote id);
29 fun err_undef kind id = error ("Undefined " ^ kind ^ ": " ^ quote id);
37 datatype entry = Entry of {next: command_id option, state: state_id option};
38 fun make_entry next state = Entry {next = next, state = state};
40 fun the_entry entries (id: command_id) =
41 (case Symtab.lookup entries id of
42 NONE => err_undef "document entry" id
43 | SOME (Entry entry) => entry);
45 fun put_entry (id: command_id, entry: entry) = Symtab.update (id, entry);
47 fun put_entry_state (id: command_id) state entries =
48 let val {next, ...} = the_entry entries id
49 in put_entry (id, make_entry next state) entries end;
51 fun reset_entry_state id = put_entry_state id NONE;
52 fun set_entry_state (id, state_id) = put_entry_state id (SOME state_id);
57 datatype document = Document of
58 {dir: Path.T, (*master directory*)
59 name: string, (*theory name*)
60 entries: entry Symtab.table}; (*unique command entries indexed by command_id, start with no_id*)
62 fun make_document dir name entries =
63 Document {dir = dir, name = name, entries = entries};
65 fun map_entries f (Document {dir, name, entries}) =
66 make_document dir name (f entries);
71 fun fold_entries id0 f (Document {entries, ...}) =
75 let val entry = the_entry entries id
76 in apply (#next entry) (f (id, entry) x) end;
77 in if Symtab.defined entries id0 then apply (SOME id0) else I end;
79 fun first_entry P (Document {entries, ...}) =
81 fun first _ NONE = NONE
82 | first prev (SOME id) =
83 let val entry = the_entry entries id
84 in if P (id, entry) then SOME (prev, id, entry) else first (SOME id) (#next entry) end;
85 in first NONE (SOME no_id) end;
90 fun insert_after (id: command_id) (id2: command_id) = map_entries (fn entries =>
91 let val {next, state} = the_entry entries id in
93 |> put_entry (id, make_entry (SOME id2) state)
94 |> put_entry (id2, make_entry next NONE)
97 fun delete_after (id: command_id) = map_entries (fn entries =>
98 let val {next, state} = the_entry entries id in
100 NONE => error ("No next entry to delete: " ^ quote id)
103 (case #next (the_entry entries id2) of
104 NONE => put_entry (id, make_entry NONE state)
105 | SOME id3 => put_entry (id, make_entry (SOME id3) state) #> reset_entry_state id3))
110 (** global configuration **)
116 val global_states = Unsynchronized.ref (Symtab.make [(no_id, Lazy.value (SOME Toplevel.toplevel))]);
120 fun define_state (id: state_id) state =
121 NAMED_CRITICAL "Isar" (fn () =>
122 Unsynchronized.change global_states (Symtab.update_new (id, state))
123 handle Symtab.DUP dup => err_dup "state" dup);
125 fun the_state (id: state_id) =
126 (case Symtab.lookup (! global_states) id of
127 NONE => err_undef "state" id
128 | SOME state => state);
137 val global_commands = Unsynchronized.ref (Symtab.make [(no_id, Toplevel.empty)]);
141 fun define_command (id: command_id) tr =
142 NAMED_CRITICAL "Isar" (fn () =>
143 Unsynchronized.change global_commands (Symtab.update_new (id, Toplevel.put_id id tr))
144 handle Symtab.DUP dup => err_dup "command" dup);
146 fun the_command (id: command_id) =
147 (case Symtab.lookup (! global_commands) id of
148 NONE => err_undef "command" id
158 val global_documents = Unsynchronized.ref (Symtab.empty: document Symtab.table);
162 fun define_document (id: document_id) document =
163 NAMED_CRITICAL "Isar" (fn () =>
164 Unsynchronized.change global_documents (Symtab.update_new (id, document))
165 handle Symtab.DUP dup => err_dup "document" dup);
167 fun the_document (id: document_id) =
168 (case Symtab.lookup (! global_documents) id of
169 NONE => err_undef "document" id
170 | SOME document => document);
176 (** main operations **)
178 (* begin/end document *)
180 val no_entries = Symtab.make [(no_id, make_entry NONE (SOME no_id))];
182 fun begin_document (id: document_id) path =
184 val (dir, name) = Thy_Load.split_thy_path path;
185 val _ = define_document id (make_document dir name no_entries);
188 fun end_document (id: document_id) =
189 NAMED_CRITICAL "Isar" (fn () =>
191 val document as Document {name, ...} = the_document id;
193 the_state (the (#state (#3 (the
194 (first_entry (fn (_, {next, ...}) => is_none next) document)))));
195 val _ = (* FIXME regular execution (??) *)
196 Future.fork (fn () =>
197 (case Lazy.force end_state of
199 (Toplevel.run_command name (Toplevel.put_id id (Toplevel.commit_exit Position.none)) st;
200 Thy_Info.touch_child_thys name;
201 Thy_Info.register_thy name)
202 | NONE => error ("Failed to finish theory " ^ quote name)));
206 (* document editing *)
210 fun is_changed entries' (id, {next = _, state}) =
211 (case try (the_entry entries') id of
213 | SOME {next = _, state = state'} => state' <> state);
215 fun new_state name (id: command_id) (state_id, updates) =
217 val state = the_state state_id;
218 val state_id' = create_id ();
219 val tr = Toplevel.put_id state_id' (the_command id);
222 (case Lazy.force state of
224 | SOME st => Toplevel.run_command name tr st));
225 val _ = define_state state_id' state';
226 in (state_id', (id, state_id') :: updates) end;
228 fun report_updates updates =
229 implode (map (fn (id, state_id) => Markup.markup (Markup.edit id state_id) "") updates)
230 |> Markup.markup Markup.assign
234 fun force_state NONE = ()
235 | force_state (SOME state_id) = ignore (Lazy.force (the_state state_id));
237 val execution = Unsynchronized.ref (Future.value ());
239 fun execute document =
240 NAMED_CRITICAL "Isar" (fn () =>
242 val old_execution = ! execution;
243 val _ = Future.cancel old_execution;
244 val new_execution = Future.fork (fn () =>
245 (Future.join_result old_execution;
246 fold_entries no_id (fn (_, {state, ...}) => fn () => force_state state) document ()));
247 in execution := new_execution end);
251 fun edit_document (old_id: document_id) (new_id: document_id) edits =
252 NAMED_CRITICAL "Isar" (fn () =>
254 val old_document as Document {name, entries = old_entries, ...} = the_document old_id;
255 val new_document as Document {entries = new_entries, ...} = old_document
256 |> fold (fn (id, SOME id2) => insert_after id id2 | (id, NONE) => delete_after id) edits;
258 val (updates, new_document') =
259 (case first_entry (is_changed old_entries) new_document of
260 NONE => ([], new_document)
261 | SOME (prev, id, _) =>
263 val prev_state_id = the (#state (the_entry new_entries (the prev)));
265 fold_entries id (new_state name o #1) new_document (prev_state_id, []);
266 val new_document' = new_document |> map_entries (fold set_entry_state updates);
267 in (rev updates, new_document') end);
269 val _ = define_document new_id new_document';
270 val _ = report_updates updates;
271 val _ = execute new_document';
278 (** concrete syntax **)
281 Outer_Syntax.internal_command "Isar.define_command"
282 (Parse.string -- Parse.string >> (fn (id, text) =>
283 Toplevel.position (Position.id_only id) o
284 Toplevel.imperative (fn () =>
285 define_command id (Outer_Syntax.prepare_command (Position.id id) text))));
288 Outer_Syntax.internal_command "Isar.begin_document"
289 (Parse.string -- Parse.string >> (fn (id, path) =>
290 Toplevel.imperative (fn () => begin_document id (Path.explode path))));
293 Outer_Syntax.internal_command "Isar.end_document"
294 (Parse.string >> (fn id => Toplevel.imperative (fn () => end_document id)));
297 Outer_Syntax.internal_command "Isar.edit_document"
298 (Parse.string -- Parse.string --
299 Parse_Value.list (Parse.string -- (Parse.string >> SOME) || Parse.string >> rpair NONE)
300 >> (fn ((id, new_id), edits) =>
301 Toplevel.position (Position.id_only new_id) o
302 Toplevel.imperative (fn () => edit_document id new_id edits)));