hcl/lib/h2-io-file.adb

494 lines
14 KiB
Ada
Raw Normal View History

with H2.Ascii;
2014-06-04 17:15:52 +00:00
separate (H2.IO)
package body File is
package Slim_Ascii is new H2.Ascii (Slim_Character);
package Wide_Ascii is new H2.Ascii (Wide_Character);
--|-----------------------------------------------------------------------
--| PRIVATE ROUTINES
--|-----------------------------------------------------------------------
procedure OS_Read_File (File: in out File_Record;
Buffer: in out System_Byte_Array;
Length: out System_Length) is
begin
OS.File.Read (File.File, Buffer, Length);
File.EOF := (Length <= 0);
end OS_Read_File;
procedure Shift_Buffer (Buffer: in out File_Buffer;
Length: in System_Length) is
pragma Inline (Shift_Buffer);
begin
Buffer.Length := Buffer.Length - Length;
Buffer.Data(Buffer.Data'First .. Buffer.Data'First + Buffer.Length - 1) := Buffer.Data(Buffer.Data'First + Length .. Buffer.Data'First + Length + Buffer.Length - 1);
end Shift_Buffer;
procedure Copy_Array (Dst: in out System_Byte_Array;
Src: in System_Byte_Array;
Length: in System_Length) is
pragma Inline (Copy_Array);
begin
Dst(Dst'First .. Dst'First + Length - 1) := Src(Src'First .. Src'First + Length - 1);
end Copy_Array;
--|-----------------------------------------------------------------------
--| OPEN AND CLOSE
--|-----------------------------------------------------------------------
2014-06-04 17:15:52 +00:00
procedure Open (File: in out File_Record;
Name: in Slim_String;
Flag: in Flag_Record;
Pool: in Storage_Pool_Pointer := null) is
begin
OS.File.Open (File.File, Name, Flag, Pool => Pool);
File.Rbuf.Length := 0;
File.Wbuf.Length := 0;
File.EOF := Standard.False;
2014-06-04 17:15:52 +00:00
end Open;
procedure Open (File: in out File_Record;
Name: in Wide_String;
Flag: in Flag_Record;
Pool: in Storage_Pool_Pointer := null) is
begin
OS.File.Open (File.File, Name, Flag, Pool => Pool);
File.Rbuf.Length := 0;
File.Wbuf.Length := 0;
File.EOF := Standard.False;
2014-06-04 17:15:52 +00:00
end Open;
procedure Close (File: in out File_Record) is
begin
Flush (File);
OS.File.Close (File.File);
2014-06-04 17:15:52 +00:00
File.File := null;
end Close;
--|-----------------------------------------------------------------------
--| READ SLIM STRING
--|-----------------------------------------------------------------------
2014-06-04 17:15:52 +00:00
procedure Read (File: in out File_Record;
Buffer: in out Slim_String;
Length: out System_Length) is
pragma Assert (Buffer'Length > 0);
Outbuf: System_Byte_Array (Buffer'Range);
for Outbuf'Address use Buffer'Address;
Rbuf: File_Buffer renames File.Rbuf;
L1, L2: System_Length;
2014-06-04 17:15:52 +00:00
begin
if Rbuf.Length <= 0 and then File.EOF then
-- raise EOF EXCEPTION. ???
Length := 0;
return;
end if;
if Outbuf'Length >= Rbuf.Data'Length then
-- The output buffer size if greater than the internal buffer size.
L1 := Rbuf.Length;
if L1 < Outbuf'Length then
-- Read into the tail of the output buffer if insufficient
-- data is available in the internal buffer.
OS_Read_File (File, Outbuf(Outbuf'First + L1 .. Outbuf'Last), L2);
end if;
-- Fill the head of the output buffer with the internal buffer contents
Copy_Array (Outbuf, Rbuf.Data, L1);
-- Empty the internal buffer.
Rbuf.Length := 0;
-- Set the output length
Length := L1 + L2;
else
if Rbuf.Length < Rbuf.Data'Length then
-- Attempt to fill the internal buffer. It may not get full with a single read.
OS_Read_File (File, Rbuf.Data(Rbuf.Data'First + Rbuf.Length .. Rbuf.Data'Last), L1);
Rbuf.Length := RBuf.Length + L1;
end if;
-- Determine how much need to be copied to the output buffer.
If Outbuf'Length < Rbuf.Length then
L2 := Outbuf'Length;
else
L2 := Rbuf.Length;
end if;
-- Copy the head of the internal buffer to the output buffer
Copy_Array (Outbuf, Rbuf.Data, L2);
-- Move the residue of the internal buffer to the head
Shift_Buffer (Rbuf, L2);
-- Set the output length
Length := L2;
end if;
2014-06-04 17:15:52 +00:00
end Read;
procedure Read_Line (File: in out File_Record;
Buffer: in out Slim_String;
Length: out System_Length) is
pragma Assert (Buffer'Length > 0);
Outbuf: System_Byte_Array (Buffer'Range);
for Outbuf'Address use Buffer'Address;
Rbuf: File_Buffer renames File.Rbuf;
L1, L2, K: System_Length;
2014-06-04 17:15:52 +00:00
begin
-- Unlike Read, this procedure should use the internal buffer
-- regardless of the output buffer size as the position of
-- the line terminator is unknown.
--
-- If the buffer is not large enough to hold a line, the output
-- is just truncated truncated to the buffer size.
if Rbuf.Length <= 0 and then File.EOF then
-- raise EOF EXCEPTION. ???
Length := 0;
return;
end if;
K := Outbuf'First - 1;
outer: loop
if Rbuf.Length < Rbuf.Data'Length then
-- Attempt to fill the internal buffer. It may not get full with a single read.
OS_Read_File (File, Rbuf.Data(Rbuf.Data'First + Rbuf.Length .. Rbuf.Data'Last), L1);
Rbuf.Length := Rbuf.Length + L1;
end if;
exit when Rbuf.Length <= 0;
L2 := Rbuf.Data'First + Rbuf.Length - 1; -- Last index of the internal buffer
for I in Rbuf.Data'First .. L2 loop
K := K + 1;
Outbuf(K) := Rbuf.Data(I);
if K >= Outbuf'Last or else Outbuf(K) = Slim_Ascii.Pos.LF then -- TODO: different line terminator
L1 := I - Rbuf.Data'First + 1; -- Length of data copied to the output buffer.
Shift_Buffer (Rbuf, L1); -- Shift the residue
exit outer; -- Done
end if;
end loop;
-- Empty the internal buffer;
Rbuf.Length := 0;
end loop outer;
Length := K + 1 - Outbuf'First;
end Read_Line;
--|-----------------------------------------------------------------------
--| READ WIDE STRING
--|-----------------------------------------------------------------------
procedure Read_Wide (File: in out File_Record;
Buffer: in out Wide_String;
Length: out System_Length;
Terminator: in Wide_String) is
pragma Assert (Buffer'Length > 0);
Outbuf: Wide_String renames Buffer;
Rbuf: File_Buffer renames File.Rbuf;
Inbuf: Slim_String (Rbuf.Data'Range);
for Inbuf'Address use Rbuf.Data'Address;
L1, L2, L3, L4, I, J, K: System_Length;
begin
if Rbuf.Length <= 0 and then File.EOF then
-- raise EOF EXCEPTION. ???
Length := 0;
return;
end if;
K := Outbuf'First - 1;
outer: while K < Outbuf'Last loop
if Rbuf.Length < Rbuf.Data'Length then
-- Attempt to fill the internal buffer. It may not get full with a single read.
OS_Read_File (File, Rbuf.Data(Rbuf.Data'First + Rbuf.Length .. Rbuf.Data'Last), L1);
Rbuf.Length := Rbuf.Length + L1;
end if;
exit when Rbuf.Length <= 0;
L2 := Rbuf.Data'First + Rbuf.Length - 1; -- Last index of the internal buffer
I := Rbuf.Data'First;
while I <= L2 and K < Outbuf'Last loop
L3 := Sequence_Length(Inbuf(I)); -- Required number of bytes to compose a wide character
if L3 <= 0 then
-- Potentially illegal sequence
K := K + 1;
Outbuf(K) := Wide_Ascii.Question;
I := I + 1;
else
L4 := L2 - I + 1; -- Avaliable number of bytes available in the internal buffer
if L4 < L3 then
-- Insufficient data available. Exit the inner loop to read more.
exit;
end if;
K := K + 1;
J := I + L3;
begin
Outbuf(K..K) := Slim_To_Wide(Inbuf(I .. J - 1));
exception
when others =>
Outbuf(K) := Wide_Ascii.Question;
J := I + 1; -- Override J to skip 1 byte only
end;
I := J;
end if;
if Terminator'Length > 0 and then
Outbuf(K) = Terminator(Terminator'First) then
-- TODO: compare more characters in terminator, not just the first charactrer
Shift_Buffer (Rbuf, I - Rbuf.Data'First);
exit outer;
end if;
end loop;
-- Move the residue to the front.
Shift_Buffer (Rbuf, I - Rbuf.Data'First);
end loop outer;
Length := K + 1 - Outbuf'First;
end Read_Wide;
procedure Read (File: in out File_Record;
Buffer: in out Wide_String;
Length: out System_Length) is
Terminator: Wide_String(1..0);
begin
Read_Wide (File, Buffer, Length, Terminator);
end Read;
procedure Read_Line (File: in out File_Record;
Buffer: in out Wide_String;
Length: out System_Length) is
Terminator: constant Wide_String(1..1) := (1 => Wide_Ascii.LF);
begin
Read_Wide (File, Buffer, Length, Terminator);
end Read_Line;
--|-----------------------------------------------------------------------
--| WRITE SLIM STRING
--|-----------------------------------------------------------------------
2014-06-04 17:15:52 +00:00
procedure Write (File: in out File_Record;
Buffer: in Slim_String;
Length: out System_Length) is
Wbuf: File_Buffer renames File.Wbuf;
Inbuf: System_Byte_Array (Buffer'Range);
for Inbuf'Address use Buffer'Address;
F, L: System_Length;
2014-06-04 17:15:52 +00:00
begin
-- This procedure attempts to write as many bytes as requested.
-- However, under a certain condition, it may not be able to
-- process the input buffer in full.
if Wbuf.Length > 0 then
-- Some residue data in the internal buffer.
if Inbuf'Length <= Wbuf.Data'Length - Wbuf.Length then
-- Copy the input to the internal buffer to reduce OS calls
F := Wbuf.Data'First + Wbuf.Length - 1;
L := F + Inbuf'Length - 1;
Wbuf.Data(F .. L) := Inbuf;
Flush (File);
Length := Inbuf'Length;
return;
end if;
-- Flush the residue first.
Flush (File);
end if;
L := 0;
while L < Inbuf'Length loop
--begin
OS.File.Write (File.File, Inbuf, F);
--exception
-- when OS.Would_Block_Exception =>
-- -- Cannot write the input in full.
-- -- Copy some to to the internal buffer
-- L := L + as much as copied;
-- exit;
-- when others =>
-- raise;
--end;
L := L + F;
end loop;
Length := L;
2014-06-04 17:15:52 +00:00
end Write;
procedure Write_Line (File: in out File_Record;
Buffer: in Slim_String;
Length: out System_Length) is
Wbuf: File_Buffer renames File.Wbuf;
Inbuf: System_Byte_Array (Buffer'Range);
for Inbuf'Address use Buffer'Address;
I, J, LF: System_Length;
begin
-- This procedure attempts to write the input up to the last line
-- terminator. It buffers the remaining input after the terminator.
-- The input may not include any line terminators.
LF := Wbuf.Data'First - 1;
I := Inbuf'First - 1;
while I < Inbuf'Last loop
I := I + 1;
J := Wbuf.Data'First + Wbuf.Length;
Wbuf.Data(J) := Inbuf(I);
Wbuf.Length := Wbuf.Length + 1;
if Wbuf.Data(J) = Slim_Ascii.Pos.LF then -- TODO: different line terminator
-- Remeber the index of the line terminator
LF := J;
end if;
if Wbuf.Length >= Wbuf.Data'Length then
-- The internal write buffer is full.
Flush (File);
LF := Wbuf.Data'First - 1;
end if;
end loop;
-- The line terminator was found. Write up to the terminator.
-- Keep the rest in the internal buffer.
while LF in Wbuf.Data'First .. Wbuf.Length loop
OS.File.Write (File.File, Wbuf.Data(Wbuf.Data'First .. LF), J);
Shift_Buffer (Wbuf, J);
LF := LF - J;
end loop;
Length := I - Inbuf'First + 1;
end Write_Line;
--|-----------------------------------------------------------------------
--| WRITE WIDE STRING
--|-----------------------------------------------------------------------
2014-06-04 17:15:52 +00:00
procedure Write (File: in out File_Record;
Buffer: in Wide_String;
Length: out System_Length) is
Wbuf: File_Buffer renames File.Wbuf;
F, L, I: System_Length;
2014-06-04 17:15:52 +00:00
begin
I := Buffer'First - 1;
while I < Buffer'Last loop
I := I + 1;
declare
Tmp: Slim_String := Wide_To_Slim(Buffer(I..I));
Tmp2: System_Byte_Array(Tmp'Range);
for Tmp2'Address use Tmp'Address;
begin
F := Wbuf.Data'First + Wbuf.Length;
L := F + Tmp2'Length - 1;
if L > Wbuf.Data'Last then
-- The multi-byte sequence for the current character
-- can't fit into the internal buffer. Flush the
-- buffer and attempt to fit it in.
Flush (File);
F := Wbuf.Data'First;
L := F + Tmp2'Length - 1;
end if;
Wbuf.Data(F..L) := Tmp2;
Wbuf.Length := Wbuf.Length + Tmp2'Length;
end;
end loop;
Flush (File);
Length := I - Buffer'First + 1;
2014-06-04 17:15:52 +00:00
end Write;
procedure Write_Line (File: in out File_Record;
Buffer: in Wide_String;
Length: out System_Length) is
Wbuf: File_Buffer renames File.Wbuf;
F, L, I, LF: System_Length;
begin
LF := Wbuf.Data'First - 1;
I := Buffer'First - 1;
while I < Buffer'Last loop
I := I + 1;
declare
Tmp: Slim_String := Wide_To_Slim(Buffer(I..I));
Tmp2: System_Byte_Array(Tmp'Range);
for Tmp2'Address use Tmp'Address;
begin
F := Wbuf.Data'First + Wbuf.Length;
L := F + Tmp2'Length - 1;
if L > Wbuf.Data'Last then
-- The multi-byte sequence for the current character
-- can't fit into the internal buffer. Flush the
-- buffer and attempt to fit it in.
Flush (File);
F := Wbuf.Data'First;
L := F + Tmp2'Length - 1;
LF := Wbuf.Data'First - 1;
end if;
Wbuf.Data(F..L) := Tmp2;
Wbuf.Length := Wbuf.Length + Tmp2'Length;
if Buffer(I) = Wide_Ascii.LF then -- TODO: different line terminator
LF := L;
end if;
end;
end loop;
-- The line terminator was found. Write up to the terminator.
-- Keep the rest in the internal buffer.
while LF in Wbuf.Data'First .. Wbuf.Length loop
OS.File.Write (File.File, Wbuf.Data(Wbuf.Data'First .. LF), L);
Shift_Buffer (Wbuf, L);
LF := LF - L;
end loop;
Length := I - Buffer'First + 1;
end Write_Line;
--|-----------------------------------------------------------------------
--| FLUSH AND DRAIN
--|-----------------------------------------------------------------------
2014-06-04 17:15:52 +00:00
procedure Flush (File: in out File_Record) is
Wbuf: File_Buffer renames File.Wbuf;
Length: System_Length;
2014-06-04 17:15:52 +00:00
begin
while Wbuf.Length > 0 loop
--begin
OS.File.Write (File.File, Wbuf.Data(Wbuf.Data'First .. Wbuf.Length), Length);
--exception
-- when Would_Block_Exception =>
-- -- Flush must write all it can.
-- null;
-- when others =>
-- raise;
--end;
Shift_Buffer (Wbuf, Length);
end loop;
2014-06-04 17:15:52 +00:00
end Flush;
procedure Drain (File: in out File_Record) is
begin
File.Wbuf.Length := 0;
end Drain;
2014-06-04 17:15:52 +00:00
end File;