merged
authorwenzelm
Sat, 25 Jun 2022 10:05:43 +0200
changeset 75618 85a7795675be
parent 75611 66edc020a322 (current diff)
parent 75617 be89ec4a4523 (diff)
child 75619 9639c3867b86
merged
--- a/src/Doc/Implementation/ML.thy	Sat Jun 25 06:34:11 2022 +0200
+++ b/src/Doc/Implementation/ML.thy	Sat Jun 25 10:05:43 2022 +0200
@@ -140,11 +140,12 @@
 paragraph \<open>Scopes.\<close>
 text \<open>
   Apart from very basic library modules, ML structures are not ``opened'', but
-  names are referenced with explicit qualification, as in \<^ML>\<open>Syntax.string_of_term\<close> for example. When devising names for structures and
-  their components it is important to aim at eye-catching compositions of both
-  parts, because this is how they are seen in the sources and documentation.
-  For the same reasons, aliases of well-known library functions should be
-  avoided.
+  names are referenced with explicit qualification, as in
+  \<^ML>\<open>Syntax.string_of_term\<close> for example. When devising names for
+  structures and their components it is important to aim at eye-catching
+  compositions of both parts, because this is how they are seen in the sources
+  and documentation. For the same reasons, aliases of well-known library
+  functions should be avoided.
 
   Local names of function abstraction or case/let bindings are typically
   shorter, sometimes using only rudiments of ``words'', while still avoiding
@@ -185,8 +186,10 @@
 text \<open>
   Here are some specific name forms that occur frequently in the sources.
 
-  \<^item> A function that maps \<^ML_text>\<open>foo\<close> to \<^ML_text>\<open>bar\<close> is called \<^ML_text>\<open>foo_to_bar\<close> or \<^ML_text>\<open>bar_of_foo\<close> (never \<^ML_text>\<open>foo2bar\<close>, nor
-  \<^ML_text>\<open>bar_from_foo\<close>, nor \<^ML_text>\<open>bar_for_foo\<close>, nor \<^ML_text>\<open>bar4foo\<close>).
+  \<^item> A function that maps \<^ML_text>\<open>foo\<close> to \<^ML_text>\<open>bar\<close> is called
+  \<^ML_text>\<open>foo_to_bar\<close> or \<^ML_text>\<open>bar_of_foo\<close> (never
+  \<^ML_text>\<open>foo2bar\<close>, nor \<^ML_text>\<open>bar_from_foo\<close>, nor
+  \<^ML_text>\<open>bar_for_foo\<close>, nor \<^ML_text>\<open>bar4foo\<close>).
 
   \<^item> The name component \<^ML_text>\<open>legacy\<close> means that the operation is about to
   be discontinued soon.
@@ -206,7 +209,8 @@
     \<^item> theories are called \<^ML_text>\<open>thy\<close>, rarely \<^ML_text>\<open>theory\<close>
     (never \<^ML_text>\<open>thry\<close>)
 
-    \<^item> proof contexts are called \<^ML_text>\<open>ctxt\<close>, rarely \<^ML_text>\<open>context\<close> (never \<^ML_text>\<open>ctx\<close>)
+    \<^item> proof contexts are called \<^ML_text>\<open>ctxt\<close>, rarely \<^ML_text>\<open>context\<close>
+    (never \<^ML_text>\<open>ctx\<close>)
 
     \<^item> generic contexts are called \<^ML_text>\<open>context\<close>
 
@@ -547,9 +551,10 @@
 \<close>
 
 text \<open>
-  Here the ML environment is already managed by Isabelle, i.e.\ the \<^ML>\<open>factorial\<close> function is not yet accessible in the preceding paragraph, nor in
-  a different theory that is independent from the current one in the import
-  hierarchy.
+  Here the ML environment is already managed by Isabelle, i.e.\ the
+  \<^ML>\<open>factorial\<close> function is not yet accessible in the preceding paragraph,
+  nor in a different theory that is independent from the current one in the
+  import hierarchy.
 
   Removing the above ML declaration from the source text will remove any trace
   of this definition, as expected. The Isabelle/ML toplevel environment is
@@ -583,8 +588,9 @@
   Two further ML commands are useful in certain situations: @{command_ref
   ML_val} and @{command_ref ML_command} are \<^emph>\<open>diagnostic\<close> in the sense that
   there is no effect on the underlying environment, and can thus be used
-  anywhere. The examples below produce long strings of digits by invoking \<^ML>\<open>factorial\<close>: @{command ML_val} takes care of printing the ML toplevel result,
-  but @{command ML_command} is silent so we produce an explicit output
+  anywhere. The examples below produce long strings of digits by invoking
+  \<^ML>\<open>factorial\<close>: @{command ML_val} takes care of printing the ML toplevel
+  result, but @{command ML_command} is silent so we produce an explicit output
   message.
 \<close>
 
@@ -894,9 +900,10 @@
 \<close>
 
 text \<open>
-  Note how \<^ML>\<open>fold (Buffer.add o string_of_int)\<close> above saves an extra \<^ML>\<open>map\<close> over the given list. This kind of peephole optimization reduces both
-  the code size and the tree structures in memory (``deforestation''), but it
-  requires some practice to read and write fluently.
+  Note how \<^ML>\<open>fold (Buffer.add o string_of_int)\<close> above saves an extra
+  \<^ML>\<open>map\<close> over the given list. This kind of peephole optimization reduces
+  both the code size and the tree structures in memory (``deforestation''),
+  but it requires some practice to read and write fluently.
 
   \<^medskip>
   The next example elaborates the idea of canonical iteration, demonstrating
@@ -939,10 +946,12 @@
   buffers that can be continued in further linear transformations, folding
   etc. Thus it is more compositional than the naive \<^ML>\<open>slow_content\<close>. As
   realistic example, compare the old-style
-  \<^ML>\<open>Term.maxidx_of_term: term -> int\<close> with the newer \<^ML>\<open>Term.maxidx_term: term -> int -> int\<close> in Isabelle/Pure.
+  \<^ML>\<open>Term.maxidx_of_term: term -> int\<close> with the newer
+  \<^ML>\<open>Term.maxidx_term: term -> int -> int\<close> in Isabelle/Pure.
 
   Note that \<^ML>\<open>fast_content\<close> above is only defined as example. In many
-  practical situations, it is customary to provide the incremental \<^ML>\<open>add_content\<close> only and leave the initialization and termination to the
+  practical situations, it is customary to provide the incremental
+  \<^ML>\<open>add_content\<close> only and leave the initialization and termination to the
   concrete application to the user.
 \<close>
 
@@ -1007,8 +1016,9 @@
 
   \begin{warn}
   Regular Isabelle/ML code should output messages exclusively by the official
-  channels. Using raw I/O on \<^emph>\<open>stdout\<close> or \<^emph>\<open>stderr\<close> instead (e.g.\ via \<^ML>\<open>TextIO.output\<close>) is apt to cause problems in the presence of parallel and
-  asynchronous processing of Isabelle theories. Such raw output might be
+  channels. Using raw I/O on \<^emph>\<open>stdout\<close> or \<^emph>\<open>stderr\<close> instead (e.g.\ via
+  \<^ML>\<open>TextIO.output\<close>) is apt to cause problems in the presence of parallel
+  and asynchronous processing of Isabelle theories. Such raw output might be
   displayed by the front-end in some system console log, with a low chance
   that the user will ever see it. Moreover, as a genuine side-effect on global
   process channels, there is no proper way to retract output when Isar command
@@ -1304,12 +1314,15 @@
   is mainly that of the list structure: individual symbols that happen to be a
   singleton string do not require extra memory in Poly/ML.\<close>
 
-  \<^descr> \<^ML>\<open>Symbol.is_letter\<close>, \<^ML>\<open>Symbol.is_digit\<close>, \<^ML>\<open>Symbol.is_quasi\<close>, \<^ML>\<open>Symbol.is_blank\<close> classify standard symbols
+  \<^descr> \<^ML>\<open>Symbol.is_letter\<close>, \<^ML>\<open>Symbol.is_digit\<close>,
+  \<^ML>\<open>Symbol.is_quasi\<close>, \<^ML>\<open>Symbol.is_blank\<close> classify standard symbols
   according to fixed syntactic conventions of Isabelle, cf.\ @{cite
   "isabelle-isar-ref"}.
 
   \<^descr> Type \<^ML_type>\<open>Symbol.sym\<close> is a concrete datatype that represents the
-  different kinds of symbols explicitly, with constructors \<^ML>\<open>Symbol.Char\<close>, \<^ML>\<open>Symbol.UTF8\<close>, \<^ML>\<open>Symbol.Sym\<close>, \<^ML>\<open>Symbol.Control\<close>, \<^ML>\<open>Symbol.Malformed\<close>.
+  different kinds of symbols explicitly, with constructors
+  \<^ML>\<open>Symbol.Char\<close>, \<^ML>\<open>Symbol.UTF8\<close>, \<^ML>\<open>Symbol.Sym\<close>,
+  \<^ML>\<open>Symbol.Control\<close>, \<^ML>\<open>Symbol.Malformed\<close>.
 
   \<^descr> \<^ML>\<open>Symbol.decode\<close> converts the string representation of a symbol into
   the datatype version.
@@ -1336,7 +1349,8 @@
   of its operations simply do not fit with important Isabelle/ML conventions
   (like ``canonical argument order'', see
   \secref{sec:canonical-argument-order}), others cause problems with the
-  parallel evaluation model of Isabelle/ML (such as \<^ML>\<open>TextIO.print\<close> or \<^ML>\<open>OS.Process.system\<close>).
+  parallel evaluation model of Isabelle/ML (such as \<^ML>\<open>TextIO.print\<close> or
+  \<^ML>\<open>OS.Process.system\<close>).
 
   Subsequently we give a brief overview of important operations on basic ML
   data types.
@@ -1370,7 +1384,8 @@
   Isabelle-specific purposes with the following implicit substructures packed
   into the string content:
 
-    \<^enum> sequence of Isabelle symbols (see also \secref{sec:symbols}), with \<^ML>\<open>Symbol.explode\<close> as key operation;
+    \<^enum> sequence of Isabelle symbols (see also \secref{sec:symbols}), with
+    \<^ML>\<open>Symbol.explode\<close> as key operation;
   
     \<^enum> XML tree structure via YXML (see also @{cite "isabelle-system"}), with
     \<^ML>\<open>YXML.parse_body\<close> as key operation.
@@ -1544,7 +1559,8 @@
 
 text \<open>
   Here the first list is treated conservatively: only the new elements from
-  the second list are inserted. The inside-out order of insertion via \<^ML>\<open>fold_rev\<close> attempts to preserve the order of elements in the result.
+  the second list are inserted. The inside-out order of insertion via
+  \<^ML>\<open>fold_rev\<close> attempts to preserve the order of elements in the result.
 
   This way of merging lists is typical for context data
   (\secref{sec:context-data}). See also \<^ML>\<open>merge\<close> as defined in
@@ -2128,8 +2144,9 @@
     \<^item> \<open>pri : int\<close> (default \<^ML>\<open>0\<close>) specifies a priority within the task
     queue.
 
-    Typically there is only little deviation from the default priority \<^ML>\<open>0\<close>. As a rule of thumb, \<^ML>\<open>~1\<close> means ``low priority" and \<^ML>\<open>1\<close> means
-    ``high priority''.
+    Typically there is only little deviation from the default priority
+    \<^ML>\<open>0\<close>. As a rule of thumb, \<^ML>\<open>~1\<close> means ``low priority" and
+    \<^ML>\<open>1\<close> means ``high priority''.
 
     Note that the task priority only affects the position in the queue, not
     the thread priority. When a worker thread picks up a task for processing,
@@ -2184,10 +2201,11 @@
   There is very low overhead for this proforma wrapping of strict values as
   futures.
 
-  \<^descr> \<^ML>\<open>Future.map\<close>~\<open>f x\<close> is a fast-path implementation of \<^ML>\<open>Future.fork\<close>~\<open>(fn () => f (\<close>\<^ML>\<open>Future.join\<close>~\<open>x))\<close>, which avoids the full
-  overhead of the task queue and worker-thread farm as far as possible. The
-  function \<open>f\<close> is supposed to be some trivial post-processing or projection of
-  the future result.
+  \<^descr> \<^ML>\<open>Future.map\<close>~\<open>f x\<close> is a fast-path implementation of
+  \<^ML>\<open>Future.fork\<close>~\<open>(fn () => f (\<close>\<^ML>\<open>Future.join\<close>~\<open>x))\<close>, which avoids
+  the full overhead of the task queue and worker-thread farm as far as
+  possible. The function \<open>f\<close> is supposed to be some trivial post-processing or
+  projection of the future result.
 
   \<^descr> \<^ML>\<open>Future.cancel\<close>~\<open>x\<close> cancels the task group of the given future, using
   \<^ML>\<open>Future.cancel_group\<close> below.
--- a/src/HOL/Import/import_rule.ML	Sat Jun 25 06:34:11 2022 +0200
+++ b/src/HOL/Import/import_rule.ML	Sat Jun 25 10:05:43 2022 +0200
@@ -441,7 +441,7 @@
   end
 
 fun process_file path thy =
-  (thy, init_state) |> File.fold_lines process_line path |> fst
+  #1 (fold process_line (File.read_lines path) (thy, init_state))
 
 val _ = Outer_Syntax.command \<^command_keyword>\<open>import_file\<close>
   "import a recorded proof file"
--- a/src/HOL/Tools/sat_solver.ML	Sat Jun 25 06:34:11 2022 +0200
+++ b/src/HOL/Tools/sat_solver.ML	Sat Jun 25 10:05:43 2022 +0200
@@ -129,32 +129,32 @@
           error "formula is not in CNF"
         | write_formula (BoolVar i) =
           (i>=1 orelse error "formula contains a variable index less than 1";
-           File.output out (string_of_int i))
+           File_Stream.output out (string_of_int i))
         | write_formula (Not (BoolVar i)) =
-          (File.output out "-";
+          (File_Stream.output out "-";
            write_formula (BoolVar i))
         | write_formula (Not _) =
           error "formula is not in CNF"
         | write_formula (Or (fm1, fm2)) =
           (write_formula fm1;
-           File.output out " ";
+           File_Stream.output out " ";
            write_formula fm2)
         | write_formula (And (fm1, fm2)) =
           (write_formula fm1;
-           File.output out " 0\n";
+           File_Stream.output out " 0\n";
            write_formula fm2)
       val fm'               = cnf_True_False_elim fm
       val number_of_vars    = maxidx fm'
       val number_of_clauses = cnf_number_of_clauses fm'
     in
-      File.output out "c This file was generated by SAT_Solver.write_dimacs_cnf_file\n";
-      File.output out ("p cnf " ^ string_of_int number_of_vars ^ " " ^
+      File_Stream.output out "c This file was generated by SAT_Solver.write_dimacs_cnf_file\n";
+      File_Stream.output out ("p cnf " ^ string_of_int number_of_vars ^ " " ^
                             string_of_int number_of_clauses ^ "\n");
       write_formula fm';
-      File.output out " 0\n"
+      File_Stream.output out " 0\n"
     end
   in
-    File.open_output write_cnf_file path
+    File_Stream.open_output write_cnf_file path
   end;
 
 (* ------------------------------------------------------------------------- *)
@@ -169,54 +169,54 @@
     fun write_sat_file out =
     let
       fun write_formula True =
-          File.output out "*()"
+          File_Stream.output out "*()"
         | write_formula False =
-          File.output out "+()"
+          File_Stream.output out "+()"
         | write_formula (BoolVar i) =
           (i>=1 orelse error "formula contains a variable index less than 1";
-           File.output out (string_of_int i))
+           File_Stream.output out (string_of_int i))
         | write_formula (Not (BoolVar i)) =
-          (File.output out "-";
+          (File_Stream.output out "-";
            write_formula (BoolVar i))
         | write_formula (Not fm) =
-          (File.output out "-(";
+          (File_Stream.output out "-(";
            write_formula fm;
-           File.output out ")")
+           File_Stream.output out ")")
         | write_formula (Or (fm1, fm2)) =
-          (File.output out "+(";
+          (File_Stream.output out "+(";
            write_formula_or fm1;
-           File.output out " ";
+           File_Stream.output out " ";
            write_formula_or fm2;
-           File.output out ")")
+           File_Stream.output out ")")
         | write_formula (And (fm1, fm2)) =
-          (File.output out "*(";
+          (File_Stream.output out "*(";
            write_formula_and fm1;
-           File.output out " ";
+           File_Stream.output out " ";
            write_formula_and fm2;
-           File.output out ")")
+           File_Stream.output out ")")
       (* optimization to make use of n-ary disjunction/conjunction *)
       and write_formula_or (Or (fm1, fm2)) =
           (write_formula_or fm1;
-           File.output out " ";
+           File_Stream.output out " ";
            write_formula_or fm2)
         | write_formula_or fm =
           write_formula fm
       and write_formula_and (And (fm1, fm2)) =
           (write_formula_and fm1;
-           File.output out " ";
+           File_Stream.output out " ";
            write_formula_and fm2)
         | write_formula_and fm =
           write_formula fm
       val number_of_vars = Int.max (maxidx fm, 1)
     in
-      File.output out "c This file was generated by SAT_Solver.write_dimacs_sat_file\n";
-      File.output out ("p sat " ^ string_of_int number_of_vars ^ "\n");
-      File.output out "(";
+      File_Stream.output out "c This file was generated by SAT_Solver.write_dimacs_sat_file\n";
+      File_Stream.output out ("p sat " ^ string_of_int number_of_vars ^ "\n");
+      File_Stream.output out "(";
       write_formula fm;
-      File.output out ")\n"
+      File_Stream.output out ")\n"
     end
   in
-    File.open_output write_sat_file path
+    File_Stream.open_output write_sat_file path
   end;
 
 (* ------------------------------------------------------------------------- *)
--- a/src/Pure/General/bytes.ML	Sat Jun 25 06:34:11 2022 +0200
+++ b/src/Pure/General/bytes.ML	Sat Jun 25 10:05:43 2022 +0200
@@ -194,7 +194,7 @@
 (* IO *)
 
 fun read_block limit =
-  File.input_size (if limit < 0 then chunk_size else Int.min (chunk_size, limit));
+  File_Stream.input_size (if limit < 0 then chunk_size else Int.min (chunk_size, limit));
 
 fun read_stream limit stream =
   let
@@ -205,11 +205,11 @@
   in read empty end;
 
 fun write_stream stream bytes =
-  File.outputs stream (contents bytes);
+  File_Stream.outputs stream (contents bytes);
 
-fun read path = File.open_input (fn stream => read_stream ~1 stream) path;
+fun read path = File_Stream.open_input (fn stream => read_stream ~1 stream) path;
 
-fun write path bytes = File.open_output (fn stream => write_stream stream bytes) path;
+fun write path bytes = File_Stream.open_output (fn stream => write_stream stream bytes) path;
 
 
 (* ML pretty printing *)
--- a/src/Pure/General/file.ML	Sat Jun 25 06:34:11 2022 +0200
+++ b/src/Pure/General/file.ML	Sat Jun 25 10:05:43 2022 +0200
@@ -20,25 +20,14 @@
   val is_file: Path.T -> bool
   val check_dir: Path.T -> Path.T
   val check_file: Path.T -> Path.T
-  val open_dir: (OS.FileSys.dirstream -> 'a) -> Path.T -> 'a
-  val open_input: (BinIO.instream -> 'a) -> Path.T -> 'a
-  val open_output: (BinIO.outstream -> 'a) -> Path.T -> 'a
-  val open_append: (BinIO.outstream -> 'a) -> Path.T -> 'a
   val fold_dir: (string -> 'a -> 'a) -> Path.T -> 'a -> 'a
   val read_dir: Path.T -> string list
-  val input: BinIO.instream -> string
-  val input_size: int -> BinIO.instream -> string
-  val input_all: BinIO.instream -> string
-  val fold_lines: (string -> 'a -> 'a) -> Path.T -> 'a -> 'a
+  val read: Path.T -> string
   val read_lines: Path.T -> string list
-  val read: Path.T -> string
   val write: Path.T -> string -> unit
   val append: Path.T -> string -> unit
-  val output: BinIO.outstream -> string -> unit
-  val outputs: BinIO.outstream -> string list -> unit
   val write_list: Path.T -> string list -> unit
   val append_list: Path.T -> string list -> unit
-  val write_buffer: Path.T -> Buffer.T -> unit
   val eq: Path.T * Path.T -> bool
 end;
 
@@ -94,31 +83,10 @@
   else error ("No such file: " ^ Path.print (Path.expand path));
 
 
-(* open streams *)
-
-local
-
-fun with_file open_file close_file f =
-  Thread_Attributes.uninterruptible (fn restore_attributes => fn path =>
-    let
-      val file = open_file path;
-      val result = Exn.capture (restore_attributes f) file;
-    in close_file file; Exn.release result end);
-
-in
-
-fun open_dir f = with_file OS.FileSys.openDir OS.FileSys.closeDir f o platform_path;
-fun open_input f = with_file BinIO.openIn BinIO.closeIn f o platform_path;
-fun open_output f = with_file BinIO.openOut BinIO.closeOut f o platform_path;
-fun open_append f = with_file BinIO.openAppend BinIO.closeOut f o platform_path;
-
-end;
-
-
 (* directory content *)
 
 fun fold_dir f path a =
-  check_dir path |> open_dir (fn stream =>
+  check_dir path |> File_Stream.open_dir (fn stream =>
     let
       fun read x =
         (case OS.FileSys.readDir stream of
@@ -129,50 +97,21 @@
 fun read_dir path = sort_strings (fold_dir cons path []);
 
 
-(* input *)
-
-val input = Byte.bytesToString o BinIO.input;
-fun input_size n stream = Byte.bytesToString (BinIO.inputN (stream, n));
-val input_all = Byte.bytesToString o BinIO.inputAll;
+(* read *)
 
-(*
-  scalable iterator:
-  . avoid size limit of TextIO.inputAll and overhead of many TextIO.inputLine
-  . optional terminator at end-of-input
-*)
-fun fold_lines f path a = open_input (fn file =>
-  let
-    fun read buf x =
-      (case input file of
-        "" => (case Buffer.content buf of "" => x | line => f line x)
-      | input =>
-          (case String.fields (fn c => c = #"\n") input of
-            [rest] => read (Buffer.add rest buf) x
-          | line :: more => read_more more (f (Buffer.content (Buffer.add line buf)) x)))
-    and read_more [rest] x = read (Buffer.add rest Buffer.empty) x
-      | read_more (line :: more) x = read_more more (f line x);
-  in read Buffer.empty a end) path;
+val read = File_Stream.open_input File_Stream.input_all;
 
-fun read_lines path = rev (fold_lines cons path []);
-
-val read = open_input input_all;
+val read_lines = Bytes.read #> Bytes.trim_split_lines;
 
 
-(* output *)
+(* write *)
 
-fun output file txt = BinIO.output (file, Byte.stringToBytes txt);
-val outputs = List.app o output;
-
-fun output_list txts file = List.app (output file) txts;
-fun write_list path txts = open_output (output_list txts) path;
-fun append_list path txts = open_append (output_list txts) path;
+fun write_list path ss = File_Stream.open_output (fn stream => File_Stream.outputs stream ss) path;
+fun append_list path ss = File_Stream.open_append (fn stream => File_Stream.outputs stream ss) path;
 
 fun write path txt = write_list path [txt];
 fun append path txt = append_list path [txt];
 
-fun write_buffer path buf =
-  open_output (fn file => List.app (output file) (Buffer.contents buf)) path;
-
 
 (* eq *)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Pure/General/file_stream.ML	Sat Jun 25 10:05:43 2022 +0200
@@ -0,0 +1,58 @@
+(*  Title:      Pure/General/file_stream.ML
+    Author:     Makarius
+
+File-system stream operations.
+*)
+
+signature FILE_STREAM =
+sig
+  val open_dir: (OS.FileSys.dirstream -> 'a) -> Path.T -> 'a
+  val open_input: (BinIO.instream -> 'a) -> Path.T -> 'a
+  val open_output: (BinIO.outstream -> 'a) -> Path.T -> 'a
+  val open_append: (BinIO.outstream -> 'a) -> Path.T -> 'a
+  val input: BinIO.instream -> string
+  val input_size: int -> BinIO.instream -> string
+  val input_all: BinIO.instream -> string
+  val output: BinIO.outstream -> string -> unit
+  val outputs: BinIO.outstream -> string list -> unit
+end;
+
+structure File_Stream: FILE_STREAM =
+struct
+
+(* open streams *)
+
+local
+
+val platform_path = ML_System.platform_path o Path.implode o Path.expand;
+
+fun with_file open_file close_file f =
+  Thread_Attributes.uninterruptible (fn restore_attributes => fn path =>
+    let
+      val file = open_file path;
+      val result = Exn.capture (restore_attributes f) file;
+    in close_file file; Exn.release result end);
+
+in
+
+fun open_dir f = with_file OS.FileSys.openDir OS.FileSys.closeDir f o platform_path;
+fun open_input f = with_file BinIO.openIn BinIO.closeIn f o platform_path;
+fun open_output f = with_file BinIO.openOut BinIO.closeOut f o platform_path;
+fun open_append f = with_file BinIO.openAppend BinIO.closeOut f o platform_path;
+
+end;
+
+
+(* input *)
+
+val input = Byte.bytesToString o BinIO.input;
+fun input_size n stream = Byte.bytesToString (BinIO.inputN (stream, n));
+val input_all = Byte.bytesToString o BinIO.inputAll;
+
+
+(* output *)
+
+fun output file txt = BinIO.output (file, Byte.stringToBytes txt);
+val outputs = List.app o output;
+
+end;
--- a/src/Pure/PIDE/byte_message.ML	Sat Jun 25 06:34:11 2022 +0200
+++ b/src/Pure/PIDE/byte_message.ML	Sat Jun 25 10:05:43 2022 +0200
@@ -29,7 +29,8 @@
 
 val write = List.app o Bytes.write_stream;
 
-fun write_yxml stream tree = YXML.traverse (fn s => fn () => File.output stream s) tree ();
+fun write_yxml stream tree =
+  YXML.traverse (fn s => fn () => File_Stream.output stream s) tree ();
 
 fun flush stream = ignore (try BinIO.flushOut stream);
 
--- a/src/Pure/PIDE/yxml.ML	Sat Jun 25 06:34:11 2022 +0200
+++ b/src/Pure/PIDE/yxml.ML	Sat Jun 25 10:05:43 2022 +0200
@@ -89,8 +89,8 @@
 val string_of = string_of_body o single;
 
 fun write_body path body =
-  path |> File.open_output (fn file =>
-    fold (traverse (fn s => fn () => File.output file s)) body ());
+  path |> File_Stream.open_output (fn file =>
+    fold (traverse (fn s => fn () => File_Stream.output file s)) body ());
 
 
 (* markup elements *)
--- a/src/Pure/ROOT.ML	Sat Jun 25 06:34:11 2022 +0200
+++ b/src/Pure/ROOT.ML	Sat Jun 25 10:05:43 2022 +0200
@@ -78,8 +78,9 @@
 ML_file "General/path.ML";
 ML_file "General/url.ML";
 ML_file "System/bash.ML";
+ML_file "General/file_stream.ML";
+ML_file "General/bytes.ML";
 ML_file "General/file.ML";
-ML_file "General/bytes.ML";
 ML_file "General/long_name.ML";
 ML_file "General/binding.ML";
 ML_file "General/seq.ML";
--- a/src/Tools/cache_io.ML	Sat Jun 25 06:34:11 2022 +0200
+++ b/src/Tools/cache_io.ML	Sat Jun 25 10:05:43 2022 +0200
@@ -39,7 +39,7 @@
   let
     val _ = File.write in_path str
     val (out2, rc) = Isabelle_System.bash_output (make_cmd in_path out_path)
-    val out1 = the_default [] (try (rev o File.fold_lines cons out_path) [])
+    val out1 = the_default [] (try File.read_lines out_path)
   in {output = split_lines out2, redirected_output = out1, return_code = rc} end
 
 fun run make_cmd str =
@@ -80,7 +80,7 @@
               let val (key, l1, l2) = split line
               in ((i+1, l+l1+l2+1), Symtab.update (key, (i+1, l1, l2)) tab) end
             else ((i+1, l), tab)
-        in apfst fst (File.fold_lines parse cache_path ((1, 1), Symtab.empty)) end
+        in apfst fst (fold parse (File.read_lines cache_path) ((1, 1), Symtab.empty)) end
       else (1, Symtab.empty)
   in Cache {path = cache_path, table = Synchronized.var "Cache_IO" table} end
 
@@ -98,7 +98,7 @@
               else if i < p + len2 then (i+1, apsnd (cons line) xsp)
               else (i, xsp)
             val (out, err) =
-              apply2 rev (snd (File.fold_lines load cache_path (1, ([], []))))
+              apply2 rev (snd (fold load (File.read_lines cache_path) (1, ([], []))))
           in ((SOME {output = err, redirected_output = out, return_code = 0}, key), tab) end))
   end