(* Lightweight thread library for Objective Caml * http://www.ocsigen.org/lwt * Interface Lwt_text * Copyright (C) 2009 Jérémie Dimino * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, with linking exceptions; * either version 2.1 of the License, or (at your option) any later * version. See COPYING file for details. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. *) open Lwt open Lwt_io (* +-----------------------------------------------------------------+ | Types and creation | +-----------------------------------------------------------------+ *) type coder = | Encoder of Encoding.encoder | Decoder of Encoding.decoder type 'a channel = { channel : 'a Lwt_io.channel; encoding : Text.t; coder : coder; strict : bool; } type input_channel = Lwt_io.input channel type output_channel = Lwt_io.output channel let encoder = function | Encoder e -> e | Decoder _ -> assert false let decoder = function | Encoder _ -> assert false | Decoder d -> d #if ocaml_version >= (3, 13) let make : type m. ?strict : bool -> ?encoding : string -> m Lwt_io.channel -> m channel = fun ?(strict=false) ?(encoding=Encoding.system) ch -> #else let make ?(strict=false) ?(encoding=Encoding.system) ch = #endif { channel = ch; encoding = encoding; strict = strict; coder = match Lwt_io.mode ch with | Input -> Decoder(Encoding.decoder encoding) | Output -> Encoder(Encoding.encoder(if strict then encoding else encoding ^ "//TRANSLIT")) } let byte_channel ch = ch.channel let encoding ch = ch.encoding let close ch = Lwt_io.close ch.channel let flush ch = Lwt_io.flush ch.channel let atomic f ch = Lwt_io.atomic (fun ch' -> f { ch with channel = ch' }) ch.channel let open_file ?buffer_size ?strict ?encoding ?flags ?perm ~mode name = lwt ch = Lwt_io.open_file ?flags ?perm ~mode name in return (make ?strict ?encoding ch) let with_file ?buffer_size ?strict ?encoding ?flags ?perm ~mode name f = Lwt_io.with_file ?flags ?perm ~mode name (fun ch -> f (make ?strict ?encoding ch)) module Primitives = struct (* +---------------------------------------------------------------+ | Primitives for reading | +---------------------------------------------------------------+ *) let rec read_char da strict decoder = let ptr = da.da_ptr and max = da.da_max in if ptr = max then da.da_perform () >>= function | 0 -> raise_lwt End_of_file | _ -> read_char da strict decoder else match Encoding_bigarray.decode decoder da.da_buffer ptr (max - ptr) with | Encoding.Dec_ok(code, count) -> da.da_ptr <- ptr + count; return (Text.char code) | Encoding.Dec_need_more -> da.da_perform () >>= begin function | 0 -> if strict then raise_lwt (Failure "Lwt_text.read_char: unterminated multibyte sequence") else begin da.da_ptr <- ptr + 1; return (Text.char (Char.code da.da_buffer.{ptr})) end | _ -> read_char da strict decoder end | Encoding.Dec_error -> if strict then raise_lwt (Failure "Lwt_text.read_char: unterminated multibyte sequence") else begin da.da_ptr <- ptr + 1; return (Text.char (Char.code da.da_buffer.{ptr})) end let read_char_opt da strict decoder = try_lwt read_char da strict decoder >|= fun ch -> Some ch with | End_of_file -> return None | exn -> raise_lwt exn let rec read_all da strict decoder buf = lwt ch = read_char da strict decoder in Buffer.add_string buf ch; read_all da strict decoder buf let rec read_count da strict decoder buf = function | 0 -> return (Buffer.contents buf) | n -> lwt ch = read_char da strict decoder in Buffer.add_string buf ch; read_count da strict decoder buf (n - 1) let read count da strict decoder = match count with | None -> let buf = Buffer.create 512 in begin try_lwt read_all da strict decoder buf with | End_of_file -> return (Buffer.contents buf) end | Some 0 -> return "" | Some 1 -> begin try_lwt read_char da strict decoder with | End_of_file -> return "" end | Some len -> let buf = Buffer.create len in begin try_lwt read_count da strict decoder buf len with | End_of_file -> return (Buffer.contents buf) end let read_line da strict decoder = let buf = Buffer.create 128 in let rec loop cr_read = try_bind (fun _ -> read_char da strict decoder) (function | "\n" -> return(Buffer.contents buf) | "\r" -> if cr_read then Buffer.add_char buf '\r'; loop true | ch -> if cr_read then Buffer.add_char buf '\r'; Buffer.add_string buf ch; loop false) (function | End_of_file -> if cr_read then Buffer.add_char buf '\r'; return(Buffer.contents buf) | exn -> raise_lwt exn) in read_char da strict decoder >>= function | "\r" -> loop true | "\n" -> return "" | ch -> Buffer.add_string buf ch; loop false let read_line_opt da strict decoder = try_lwt read_line da strict decoder >|= fun ch -> Some ch with | End_of_file -> return None | exn -> raise_lwt exn (* +---------------------------------------------------------------+ | Primitives for writing | +---------------------------------------------------------------+ *) let rec write_code da encoder code = match Encoding_bigarray.encode encoder da.da_buffer da.da_ptr (da.da_max - da.da_ptr) code with | Encoding.Enc_ok count -> da.da_ptr <- da.da_ptr + count; return () | Encoding.Enc_need_more -> lwt _ = da.da_perform () in write_code da encoder code | Encoding.Enc_error -> raise_lwt (Failure "Lwt_text: cannot encode character") let byte str pos = Char.code (String.unsafe_get str pos) let next_code str i len = let n = byte str i in let rec trail j acc = function | 0 -> (j, acc) | count -> if j = len then (i + 1, n) else let m = byte str j in if m land 0xc0 = 0x80 then trail (j + 1) ((acc lsl 6) lor (m land 0x3f)) (count - 1) else (i + 1, n) in if n land 0x80 = 0 then (i + 1, n) else if n land 0xe0 = 0xc0 then trail (i + 1) (n land 0x1f) 1 else if n land 0xf0 = 0xe0 then trail (i + 1) (n land 0x0f) 2 else if n land 0xf8 = 0xf0 then trail (i + 1) (n land 0x07) 3 else (i + 1, n) let write_char da strict encoder = function | "" -> raise_lwt (Invalid_argument "Lwt_text.write_char: empty text") | ch -> let _, code = next_code ch 0 (String.length ch) in write_code da encoder code let rec write_all da strict encoder str i len = if i = len then return () else let i, code = next_code str i len in lwt () = write_code da encoder code in write_all da strict encoder str i len let write da strict encoder txt = write_all da strict encoder txt 0 (String.length txt) let write_line da strict encoder txt = lwt () = write_all da strict encoder txt 0 (String.length txt) in write_code da encoder 10 end let read_char ic = direct_access ic.channel (fun da -> Primitives.read_char da ic.strict (decoder ic.coder)) let read_char_opt ic = direct_access ic.channel (fun da -> Primitives.read_char_opt da ic.strict (decoder ic.coder)) let read ?count ic = direct_access ic.channel (fun da -> Primitives.read count da ic.strict (decoder ic.coder)) let read_line ic = direct_access ic.channel (fun da -> Primitives.read_line da ic.strict (decoder ic.coder)) let read_line_opt ic = direct_access ic.channel (fun da -> Primitives.read_line_opt da ic.strict (decoder ic.coder)) let read_chars ic = Lwt_stream.from (fun _ -> read_char_opt ic) let read_lines ic = Lwt_stream.from (fun _ -> read_line_opt ic) let write_char oc x = direct_access oc.channel (fun da -> Primitives.write_char da oc.strict (encoder oc.coder) x) let write_line oc x = direct_access oc.channel (fun da -> Primitives.write_line da oc.strict (encoder oc.coder) x) let write oc x = direct_access oc.channel (fun da -> Primitives.write da oc.strict (encoder oc.coder) x) let write_chars oc st = Lwt_stream.iter_s (write_char oc) st let write_lines oc st = Lwt_stream.iter_s (write_line oc) st let stdin = make Lwt_io.stdin let stdout = make Lwt_io.stdout let stderr = make Lwt_io.stderr let null = make Lwt_io.null let zero = make Lwt_io.zero let fprint oc txt = write oc txt let fprintl oc txt = write_line oc txt let fprintf oc fmt = Printf.ksprintf (fun txt -> write oc txt) fmt let fprintlf oc fmt = Printf.ksprintf (fun txt -> write_line oc txt) fmt let print txt = write stdout txt let printl txt = write_line stdout txt let printf fmt = Printf.ksprintf print fmt let printlf fmt = Printf.ksprintf printl fmt let eprint txt = write stderr txt let eprintl txt = write_line stderr txt let eprintf fmt = Printf.ksprintf eprint fmt let eprintlf fmt = Printf.ksprintf eprintl fmt let ignore_close ch = ignore (close ch) let make_stream f lazy_ic = let lazy_ic = lazy(lwt ic = Lazy.force lazy_ic in Gc.finalise ignore_close ic; return ic) in Lwt_stream.from (fun _ -> lwt ic = Lazy.force lazy_ic in try_lwt f ic >|= fun x -> Some x with | End_of_file -> lwt () = close ic in return None) let lines_of_file filename = make_stream read_line (lazy(open_file ~mode:input filename)) let lines_to_file filename lines = with_file ~mode:output filename (fun oc -> write_lines oc lines) let chars_of_file filename = make_stream read_char (lazy(open_file ~mode:input filename)) let chars_to_file filename chars = with_file ~mode:output filename (fun oc -> write_chars oc chars) let hexdump_stream oc stream = write_lines oc (Lwt_stream.hexdump stream) let hexdump oc buf = hexdump_stream oc (Lwt_stream.of_string buf)