Files
mercury/tests/hard_coded/bit_buffer_test.m
Zoltan Somogyi 2bd7c5ee3e Rename X's aux modules as X_helper_N in hard_coded.
tests/hard_coded/*.m:
    Rename modules as mentioned above.

    In a few cases, where the main module's name itself had a suffix,
    such as "_mod_a" or "_main", remove that suffix. This entails
    renaming the .exp file as well. (In some cases, this meant that
    the name of a helper module was "taken over" by the main module
    of the test case.)

    Update all references to the moved modules.

    General updates to programming style, such as

    - replacing DCG notation with state var notation
    - replacing (C->T;E) with (if C then T else E)
    - moving pred/func declarations to just before their code
    - replacing io.write/io.nl sequences with io.write_line
    - replacing io.print/io.nl sequences with io.print_line
    - fixing too-long lines
    - fixing grammar errors in comments

tests/hard_coded/Mmakefile:
tests/hard_coded/Mercury.options:
    Update all references to the moved modules.

    Enable the constant_prop_int test case. The fact that it wasn't enabled
    before is probably an accident. (When constant_prop_int.m was created,
    the test case was added to a list in the Mmakefile, but that list
    was later removed due to never being referenced.)

tests/hard_coded/constant_prop_int.{m,exp}:
    Delete the calls to shift operations with negative shift amounts,
    since we have added a compile-time error for these since the test
    was originally created.
2023-06-16 08:33:22 +02:00

775 lines
27 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ts=4 sw=4 et tw=0 wm=0 ft=mercury
%---------------------------------------------------------------------------%
:- module bit_buffer_test.
:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is cc_multi.
:- implementation.
:- import_module assoc_list.
:- import_module bit_buffer.
:- import_module bit_buffer.read.
:- import_module bit_buffer.write.
:- import_module bitmap.
:- import_module bool.
:- import_module exception.
:- import_module int.
:- import_module io.file.
:- import_module list.
:- import_module pair.
:- import_module stream.
:- import_module string.
:- import_module univ.
:- type request
---> bits(word, num_bits)
; bitmap(bitmap, bit_index, num_bits)
; flush
; pad_to_byte
; check_buffer_status(stream.result(univ)).
:- type read_error
---> bits(
expected_word :: word,
found_word :: word, num_bits
)
; bitmap(
expected_bitmap :: bitmap,
found_bitmap :: bitmap,
request_size :: num_bits,
bits_read :: num_bits
)
; check_buffer_status(
expected_status :: stream.result(univ),
found_status :: stream.result(univ)
).
main(!IO) :-
% Distinctive byte patterns so we can tell where bits came from.
%
Byte1 = 0b10101010,
Byte2 = 0b11001100,
Byte3 = 0b01000110,
Byte4 = 0b10111001,
% Uncomment this to debug read errors.
% bit_buffer.read.set_logging_level(1, !IO),
some [!Seq, !ShortSeq, !LongSeq, !ShortBM, !LongBM, !ErrorSeq] (
% Test with request sequences that are a minimum of 8 bytes to
% test overflow even on 64-bit machines (buffers are at least
% as big as the word size).
io.write_string("Test reading and writing full bytes.\n", !IO),
!:Seq = condense(duplicate(4,
[bits(Byte1, 8), bits(Byte2, 8), check_buffer_status(ok),
bits(Byte3, 8), bits(0, 0), bits(Byte4, 8)]))
++ [check_buffer_status(eof)],
Seq1 = !.Seq,
test_sequence(8, !.Seq, !IO),
io.write_string("Test reading and writing partial bytes.\n", !IO),
% This also tests a request split over a flush and handling of
% a list of requests for which the length is not a multiple of
% the buffer size.
!:Seq = condense(duplicate(6,
[bits(Byte1, 7), bits(1, 1), bits(Byte2, 6),
bits(Byte3, 7), bits(Byte4, 4)])),
Seq2 = !.Seq,
test_sequence(8, !.Seq, !IO),
io.write_string(
"Test flushes when the stream is at a byte boundary and when it is not.\n",
!IO),
!:Seq = condense(duplicate(6,
[flush, bits(Byte1, 7), bits(0, 1), flush, bits(Byte2, 6),
bits(Byte3, 7), flush, bits(Byte4, 4)])),
test_sequence(8, !.Seq, !IO),
% A short simple bitmap.
%
!:Seq = [bits(Byte1, 8), bits(Byte2, 8), bits(Byte3, 8)],
BM1 = requests_to_bitmap(!.Seq),
% A longer bitmap.
%
BM2 = requests_to_bitmap(Seq1 ++ Seq2),
io.write_string("Test simple reading and writing of bitmaps.\n", !IO),
!:Seq = [bitmap(BM1, 0, num_bits(BM1))],
test_sequence(8, !.Seq, !IO),
io.write_string("Test a simple offset bitmap read.\n", !IO),
!:Seq = [bitmap(BM1, bits_per_byte,
num_bits(BM1) - bits_per_byte)],
test_sequence(8, !.Seq, !IO),
io.write_string("Test zero size requests.\n", !IO),
!:Seq = [bits(Byte2, 0), bits(Byte1, 4),
bits(Byte2, 0), bitmap(BM1, 0, 0)],
test_sequence(8, !.Seq, !IO),
io.write_string("Test pad_to_byte\n", !IO),
!:Seq = [pad_to_byte, bits(Byte1, 3), pad_to_byte, pad_to_byte,
bits(Byte2, 8), pad_to_byte, bits(Byte2, 2)],
test_sequence(8, !.Seq, !IO),
io.write_string("Test a bitmap that spans multiple buffer flushes.\n",
!IO),
!:Seq = [bitmap(BM2, 0, num_bits(BM2))],
test_sequence(8, !.Seq, !IO),
io.write_string(
"Test a bitmap starting at a position that isn't on a byte boundary.\n",
!IO),
!:Seq = [bits(Byte1, 3), bitmap(BM2, 0, num_bits(BM2))],
test_sequence(8, !.Seq, !IO),
io.write_string("Test offsets passed to put_bitmap.\n", !IO),
!:Seq = [bits(Byte1, 3), bitmap(BM2, 3, num_bits(BM2) - 3)],
test_sequence(8, !.Seq, !IO),
io.write_string("========== Read Error Tests ==========\n", !IO),
io.write_string("Test unexpected end-of-file.\n", !IO),
!:ShortSeq = Seq1,
!:LongSeq = Seq1 ++ Seq1,
test_error_sequence(io_and_bitmap, 8, !.ShortSeq, !.LongSeq, !IO),
io.write_string(
"Test read sequence of bitmaps one byte too long.\n", !IO),
!:LongBM = shrink_without_copying(copy(BM2), 136),
!:ShortBM = shrink_without_copying(copy(BM2), 128),
!:ShortSeq = [bitmap(!.ShortBM, 0, num_bits(!.ShortBM))],
!:LongSeq = [bitmap(!.LongBM, 0, num_bits(!.LongBM))],
test_error_sequence(io_and_bitmap, 8, !.ShortSeq, !.LongSeq, !IO),
io.write_string(
"Test read sequence of bitmaps one byte too long.\n", !IO),
!:LongBM = shrink_without_copying(copy(BM2), 136),
!:ShortBM = shrink_without_copying(copy(BM2), 128),
!:ShortSeq = [bitmap(!.ShortBM, 0, num_bits(!.ShortBM))],
!:LongSeq = [bitmap(!.LongBM, 0, num_bits(!.LongBM))],
test_error_sequence(io_and_bitmap, 8, !.ShortSeq, !.LongSeq, !IO),
io.write_string("Test non-zero padding bits.\n", !IO),
PaddingBitsErrorSeq = [bits(Byte1, 2), pad_to_byte],
test_error_sequence(io_and_bitmap, 8, Seq1, PaddingBitsErrorSeq, !IO),
% Test cases which only occur with bitmaps of a size not a multiple
% of bits_per_byte.
%
io.write_string("========== Bitmap error tests ==========\n", !IO),
io.write_string("Test eof when skipping padding in bitmap\n", !IO),
!:Seq = [bits(0, 7)],
!:ErrorSeq = [bits(0, 1), pad_to_byte],
test_error_sequence(bitmap_only, 8, !.Seq, !.ErrorSeq, !IO),
io.write_string("========== Argument Error Tests ==========\n", !IO),
test_error_sequence(io_and_bitmap, 8, Seq1, [bits(0, -1)], !IO),
test_error_sequence(io_and_bitmap, 8, Seq1, [bits(0, 100)], !IO),
test_error_sequence(io_and_bitmap, 8, Seq1, [bitmap(BM1, 0, -1)], !IO),
test_error_sequence(io_and_bitmap, 8, Seq1,
[bitmap(BM1, 0, 10000)], !IO),
io.write_string("========== Stream Error Tests ==========\n", !IO),
test_error_sequence(timebomb(10), 8, Seq1, Seq1, !IO),
io.write_string("Test error when refilling buffer\n", !IO),
!:Seq = [bitmap(shrink_without_copying(copy(BM2), 72), 0, 72)],
!:ErrorSeq = [bits(BM2 ^ bits(0, 32), 32),
bits(BM2 ^ bits(32, 32), 32),
check_buffer_status(ok),
bits(BM2 ^ bits(64, 8), 8)],
test_error_sequence(timebomb(8), 8, !.Seq, !.ErrorSeq, !IO)
),
io.file.remove_file(bit_buffer_test_tmp_file, _, !IO).
:- pred test_sequence(num_bytes::in, list(request)::in, io::di, io::uo) is det.
test_sequence(BufferSize, Requests0, !IO) :-
% This makes the results for bitmap and I/O buffers consistent.
Requests = Requests0 ++ [pad_to_byte],
io.format("Testing with buffer size %d.\n", [i(BufferSize)], !IO),
TempFile = bit_buffer_test_tmp_file,
io.write_string("Testing writes: [", !IO),
io.write_list(Requests, ", ", output_request, !IO),
io.write_string("]\n", !IO),
io.write_string("Expected result: ", !IO),
ExpectedBM = requests_to_bitmap(Requests),
io.write_string(to_byte_string(ExpectedBM), !IO),
io.nl(!IO),
io.flush_output(!IO),
test_writes(BufferSize, TempFile, Requests, ExpectedBM, !IO),
io.write_string("Testing reads:\n", !IO),
test_reads(BufferSize, TempFile, Requests, ExpectedBM, !IO),
io.write_string("\n", !IO),
io.flush_output(!IO).
% Read the given number of bits, then fail on the next.
:- type timer == int.
:- type error_test_type
---> io_and_bitmap
; bitmap_only
; timebomb(timer).
% SetupRequests will set up a bitmap and the file returned by
% `bit_buffer_test_tmp_file'. Requests is a list of requests
% that will result in a read error when applied to that input.
%
:- pred test_error_sequence(error_test_type::in, num_bytes::in,
list(request)::in, list(request)::in, io::di, io::uo) is cc_multi.
test_error_sequence(ErrorTestType, BufferSize,
SetupRequests0, Requests0, !IO) :-
(
ErrorTestType = io_and_bitmap,
% This makes the results for bitmap and I/O buffers consistent.
Requests = Requests0 ++ [pad_to_byte],
SetupRequests = SetupRequests0 ++ [pad_to_byte]
;
ErrorTestType = bitmap_only,
SetupRequests = SetupRequests0,
Requests = Requests0
;
ErrorTestType = timebomb(_),
SetupRequests = SetupRequests0,
Requests = Requests0
),
io.write_string("Testing sequence that should cause an error:\n", !IO),
io.write_string("Using setup requests:\n", !IO),
io.write(SetupRequests, !IO),
io.nl(!IO),
io.write_string("Using error requests:\n", !IO),
io.write(Requests, !IO),
io.nl(!IO),
ExpectedBM = requests_to_bitmap(SetupRequests),
TempFile = bit_buffer_test_tmp_file,
( if
( ErrorTestType = io_and_bitmap
; ErrorTestType = timebomb(_)
)
then
test_writes(8, TempFile, SetupRequests, ExpectedBM, !IO)
else
true
),
( if
( ErrorTestType = io_and_bitmap
; ErrorTestType = bitmap_only
)
then
check_that_error_occurs("bitmap",
test_bitmap_reads(Requests, ExpectedBM),
!IO)
else
true
),
( if
ErrorTestType = io_and_bitmap
then
check_that_error_occurs("I/O",
test_io_reads(BufferSize, TempFile, Requests),
!IO)
else
true
),
( if
ErrorTestType = timebomb(Timer)
then
check_that_error_occurs("stream read error",
test_io_timebomb_reads(BufferSize, Timer, TempFile, Requests),
!IO)
else
true
),
io.write_string("\n", !IO),
io.flush_output(!IO).
:- pred check_that_error_occurs(string::in,
pred(io, io)::(pred(di, uo) is det), io::di, io::uo) is cc_multi.
check_that_error_occurs(Desc, P, !IO) :-
Q = (pred({}::out, !.IO::di, !:IO::uo) is det :- P(!IO)),
try_io(Q, Result, !IO),
(
Result = succeeded(_),
io.write_string(Desc ++ " reads unexpectedly succeeded\n", !IO)
;
Result = exception(Error),
io.write_string(Desc ++ " reads failed as expected:\n", !IO),
io.write_line(Error, !IO)
).
:- pred output_request(request::in, io::di, io::uo) is det.
output_request(bits(Word, NumBits), !IO) :-
io.write_string("bits(", !IO),
io.write_string(int_to_base_string(Word, 2), !IO),
io.write_string(", ", !IO),
io.write_int(NumBits, !IO),
io.write_string(")", !IO).
output_request(bitmap(BM, Index, NumBits), !IO) :-
io.write_string("bitmap(", !IO),
io.write_string(bitmap.to_byte_string(BM), !IO),
io.write_string(", ", !IO),
io.write_int(Index, !IO),
io.write_string(", ", !IO),
io.write_int(NumBits, !IO),
io.write_string(")", !IO).
output_request(pad_to_byte, !IO) :-
io.write_string("pad_to_byte", !IO).
output_request(flush, !IO) :-
io.write_string("flush", !IO).
output_request(check_buffer_status(BufferStatus), !IO) :-
io.write_string("check_buffer_status(", !IO),
io.write(BufferStatus, !IO),
io.write_string(")", !IO).
:- pred test_writes(num_bytes::in, string::in, list(request)::in,
bitmap::in, io::di, io::uo) is det.
test_writes(BufferSize, FileName, Writes, ExpectedBM, !IO) :-
io.open_binary_output(FileName, WriteOpenRes, !IO),
(
WriteOpenRes = ok(WriteStream),
some [!BMBuffer, !IOBuffer] (
!:BMBuffer = new_bitmap_builder(BufferSize),
!:IOBuffer = new(BufferSize, WriteStream, !.IO),
list.foldl(do_write, Writes, !BMBuffer),
list.foldl(do_write, Writes, !IOBuffer),
finalize(!.IOBuffer, _, !:IO),
BM = finalize_to_bitmap(!.BMBuffer),
io.close_binary_output(WriteStream, !IO)
),
( if BM = ExpectedBM then
io.write_string("Collected bitmap compares OK.\n", !IO)
else
io.write_string("Collected bitmap differs: \n", !IO),
io.write_string(to_byte_string(BM), !IO),
io.nl(!IO),
io.flush_output(!IO)
),
io.open_binary_input(FileName, ReadOpenRes, !IO),
(
ReadOpenRes = ok(ReadStream),
io.read_binary_file_as_bitmap(ReadStream, BMReadResult, !IO),
(
BMReadResult = ok(ReadBM),
( if ReadBM = ExpectedBM then
io.write_string("I/O bitmap compares OK.\n", !IO),
io.flush_output(!IO)
else
io.write_string("I/O bitmap differs: \n", !IO),
io.write_string(to_byte_string(ReadBM), !IO),
io.nl(!IO),
io.flush_output(!IO)
)
;
BMReadResult = error(Error),
io.write_string(io.error_message(Error), !IO),
io.nl(!IO),
io.flush_output(!IO)
),
io.close_binary_input(ReadStream, !IO)
;
ReadOpenRes = error(Msg),
io.write_string(io.error_message(Msg), !IO),
io.nl(!IO),
io.flush_output(!IO)
)
;
WriteOpenRes = error(Msg),
io.write_string(io.error_message(Msg), !IO),
io.nl(!IO),
io.flush_output(!IO)
).
:- pred do_write(request::in,
write_buffer(S, St)::write_buffer_di,
write_buffer(S, St)::write_buffer_uo) is det
<= stream.writer(S, bitmap.slice, St).
do_write(bits(Word, NumBits), !Buffer) :-
( if NumBits = 1 then
Bit = ( if Word = 0 then no else yes ),
put_bit(Bit, !Buffer)
else if NumBits = 8 then
put_byte(Word, !Buffer)
else
put_bits(Word, NumBits, !Buffer)
).
do_write(bitmap(BM, Index, NumBits), !Buffer) :-
( if Index = 0, NumBits = BM ^ num_bits then
put_bitmap(BM, !Buffer)
else
put_bitmap(BM, Index, NumBits, !Buffer)
).
do_write(pad_to_byte, !Buffer) :-
NumPaddingBits = num_bits_to_byte_boundary(!.Buffer),
put_bits(0, NumPaddingBits, !Buffer).
do_write(flush, !Buffer) :-
flush(!Buffer).
do_write(check_buffer_status(_), !Buffer).
% Create a bitmap directly from the list of requests.
:- func requests_to_bitmap(list(request)::in) =
(bitmap::bitmap_uo) is det.
requests_to_bitmap(Requests) = !:BM :-
Size = request_list_length(Requests, 0),
!:BM = bitmap.init(Size),
list.foldl2(request_to_bitmap, Requests, 0, _, !BM).
:- func request_list_length(list(request), int) = int.
request_list_length([], L) = L.
request_list_length([Req | Reqs], L0) = L :-
( Req = bits(_, NumBits)
; Req = bitmap(_, _, NumBits)
; Req = pad_to_byte, Rem = L0 `rem` bits_per_byte,
NumBits = ( if Rem = 0 then 0 else bits_per_byte - Rem )
; Req = flush, NumBits = 0
; Req = check_buffer_status(_), NumBits = 0
),
L = request_list_length(Reqs, L0 + NumBits).
:- pred request_to_bitmap(request::in, int::in, int::out,
bitmap::bitmap_di, bitmap::bitmap_uo) is det.
request_to_bitmap(bits(Word, NumBits), !Index, !BM) :-
!:BM = !.BM ^ bits(!.Index, NumBits) := Word,
!:Index = !.Index + NumBits.
request_to_bitmap(bitmap(OtherBM, Start, NumBits), !Index, !BM) :-
!:BM = copy_bits(OtherBM, Start, !.BM, !.Index, NumBits),
!:Index = !.Index + NumBits.
request_to_bitmap(pad_to_byte, !Index, !BM) :-
Rem = !.Index `rem` bits_per_byte,
NumBits = ( if Rem = 0 then 0 else bits_per_byte - Rem ),
!:BM = !.BM ^ bits(!.Index, NumBits) := 0,
!:Index = !.Index + NumBits.
request_to_bitmap(flush, !Index, !BM).
request_to_bitmap(check_buffer_status(_), !Index, !BM).
:- pred test_reads(num_bytes::in, string::in, list(request)::in,
bitmap::in, io::di, io::uo) is det.
test_reads(BufferSize, FileName, Requests, ExpectedBM, !IO) :-
test_bitmap_reads(Requests, ExpectedBM, !IO),
test_io_reads(BufferSize, FileName, Requests, !IO).
:- pred test_bitmap_reads(list(request)::in,
bitmap::in, io::di, io::uo) is det.
test_bitmap_reads(Requests, ExpectedBM, !IO) :-
some [!BMBuffer] (
!:BMBuffer = new_bitmap_reader(ExpectedBM, 0, ExpectedBM ^ num_bits),
do_reads("bitmap", 1, Requests, !BMBuffer),
finalize(!.BMBuffer, _, _, _, _, BMNumFinalBits),
( if BMNumFinalBits = 0 then
true
else
throw(string.format("bitmap reader has %d bits left over: \n",
[i(BMNumFinalBits)]) : string)
),
io.write_string("bitmap read tests completed.\n", !IO)
).
:- pred test_io_reads(num_bytes::in, string::in, list(request)::in,
io::di, io::uo) is det.
test_io_reads(BufferSize, FileName, Requests, !IO) :-
io.open_binary_input(FileName, ReadOpenRes, !IO),
some [!IOBuffer] (
ReadOpenRes = ok(ReadStream),
!:IOBuffer = new(BufferSize, ReadStream, !.IO) : io_read_buffer,
do_reads("I/O", 1, Requests, !IOBuffer),
finalize(!.IOBuffer, _, !:IO, _, _, IONumFinalBits),
( if IONumFinalBits = 0 then
true
else
throw(string.format("I/O reader has %d bits left over: \n",
[i(IONumFinalBits)]): string)
),
io.write_string("I/O read tests completed.\n", !IO),
io.close_binary_input(ReadStream, !IO)
;
ReadOpenRes = error(Msg),
throw(Msg)
).
:- pred test_io_timebomb_reads(num_bytes::in, num_bytes::in,
string::in, list(request)::in, io::di, io::uo) is det.
test_io_timebomb_reads(BufferSize, Countdown, FileName, Requests, !IO) :-
io.open_binary_input(FileName, ReadOpenRes, !IO),
some [!ErrorBuffer] (
ReadOpenRes = ok(ReadStream),
ErrorState0 = 'new timebomb_state'(ReadStream, !.IO, Countdown),
!:ErrorBuffer = bit_buffer.read.new(BufferSize,
timebomb_byte_stream,
unsafe_promise_unique(ErrorState0)) :
read_buffer(timebomb_byte_stream,
timebomb_state, timebomb_error),
do_reads("timebomb", 1, Requests, !ErrorBuffer),
finalize(!.ErrorBuffer, _, ErrorState, _, _, ErrorNumFinalBits),
( if ErrorNumFinalBits = 0 then
true
else
throw(string.format("timebomb reader has %d bits left over: \n",
[i(ErrorNumFinalBits)]) : string)
),
det_univ_to_type(univ(ErrorState ^ timebombed_state), !:IO),
!:IO = unsafe_promise_unique(!.IO),
io.write_string("timebomb read tests completed.\n", !IO),
io.close_binary_input(ReadStream, !IO)
;
ReadOpenRes = error(Msg),
throw(Msg)
).
:- pred do_reads(string::in, int::in, list(request)::in,
read_buffer(S, St, E)::read_buffer_di,
read_buffer(S, St, E)::read_buffer_uo) is det
<= stream.bulk_reader(S, byte_index, bitmap, St, E).
do_reads(_, _, [], !Buffer).
do_reads(Desc, Index, [Req | Reqs], !Buffer) :-
do_read(Desc, Index, Req, !Buffer),
do_reads(Desc, Index + 1, Reqs, !Buffer).
:- pred do_read(string::in, int::in, request::in,
read_buffer(S, St, E)::read_buffer_di,
read_buffer(S, St, E)::read_buffer_uo) is det
<= stream.bulk_reader(S, byte_index, bitmap, St, E).
do_read(Desc, ReqIndex, bits(ExpectedWord0, NumBits), !Buffer) :-
ExpectedWord = mask_word(ExpectedWord0, NumBits),
( if NumBits = 1 then
get_bit(GetResult, !Buffer),
(
GetResult = ok(ResultBit),
ResultWord = ( if ResultBit = yes then 1 else 0 ),
( if ResultWord = ExpectedWord then
true
else
throw_read_result_error(bits(ExpectedWord, ResultWord, 1),
Desc, ReqIndex)
)
;
GetResult = eof,
throw("bit_buffer_test: unexpected eof in get_bit")
;
GetResult = error(Err),
throw(Err)
)
else
get_bits(bits_per_int - NumBits, NumBits, 0, ResultWord,
NumBitsRead, GetResult, !Buffer),
(
GetResult = ok,
( if NumBitsRead = NumBits, ExpectedWord = ResultWord then
true
else
throw_read_result_error(
bits(ExpectedWord, ResultWord, NumBits),
Desc, ReqIndex)
)
;
GetResult = error(Err),
throw(Err)
)
).
do_read(Desc, ReqIndex, bitmap(SourceBM, Index, NumBits), !Buffer) :-
some [!BM] (
!:BM = bitmap.init(SourceBM ^ num_bits),
( if Index = 0, NumBits = SourceBM ^ num_bits then
get_bitmap(!BM, BitsRead, GetResult, !Buffer)
else
get_bitmap(Index, NumBits, !BM, BitsRead, GetResult, !Buffer)
),
(
GetResult = ok,
ExpectedBM0 = bitmap.init(SourceBM ^ num_bits),
ExpectedBM = copy_bits(SourceBM, Index,
ExpectedBM0, Index, NumBits),
( if ExpectedBM = !.BM then
true
else
throw_read_result_error(
bitmap(ExpectedBM, !.BM, NumBits, BitsRead),
Desc, ReqIndex)
)
;
GetResult = error(Err),
throw_read_stream_error(Err, Desc, ReqIndex)
)
).
do_read(Desc, ReqIndex, pad_to_byte, !Buffer) :-
NumPaddingBits = num_bits_to_byte_boundary(!.Buffer),
do_read(Desc, ReqIndex, bits(0, NumPaddingBits), !Buffer).
do_read(_Desc, _Index, flush, !Buffer).
do_read(Desc, ReqIndex, check_buffer_status(ExpectedStatus), !Buffer) :-
buffer_status(FoundStatus0, !Buffer),
( FoundStatus0 = ok, FoundStatus = ok
; FoundStatus0 = eof, FoundStatus = eof
; FoundStatus0 = error(Err), FoundStatus = error(univ(Err))
),
( if ExpectedStatus = FoundStatus then
true
else
throw_read_result_error(
check_buffer_status(ExpectedStatus, FoundStatus),
Desc, ReqIndex)
).
:- type read_result_exception == pair(string, read_error).
:- pred throw_read_result_error(read_error::in, string::in, int::in)
is erroneous.
throw_read_result_error(Error, Desc, ReqIndex) :-
throw((Desc ++ ": error in request " ++ int_to_string(ReqIndex)) - Error).
:- pred throw_read_stream_error(T::in, string::in, int::in)
is erroneous.
throw_read_stream_error(Error, Desc, ReqIndex) :-
throw((Desc ++ ": error in request " ++ int_to_string(ReqIndex)) - Error).
:- func mask_word(word, num_bits) = word.
mask_word(ExpectedWord0, N) = ExpectedWord :-
( if N \= 0 then
BitMask = 1 `unchecked_left_shift` (N - 1),
BitsMask = BitMask \/ (BitMask - 1),
ExpectedWord = ExpectedWord0 /\ BitsMask
else
ExpectedWord = 0
).
:- func bit_buffer_test_tmp_file = string.
bit_buffer_test_tmp_file = "bit_buffer_test_tmp".
% A timebomb stream counts down bytes until it returns an error.
% XXX It should be possible to produce a generic version of this
% that works on all Unit types, but the current restrictions on
% instances don't allow that. Also, the Error type of the stream
% can't be exposed for the same reason.
%
:- type timebomb_byte_stream
---> timebomb_byte_stream.
:- type timebomb_state
---> some [Stream, State, Error]
timebomb_state(
timebombed_stream :: Stream,
timebombed_state :: State,
countdown :: int
) =>
(reader(Stream, byte, State, Error),
bulk_reader(Stream, int, bitmap, State, Error)).
:- type timebomb_error
---> bang
; already_exploded
; stream_error(univ).
:- instance stream.error(timebomb_error) where [
error_message(bang) = "Bang!!!",
error_message(already_exploded) = "This stream is already exploded.",
error_message(stream_error(_Univ)) = "stream_error"
].
:- instance stream.stream(timebomb_byte_stream, timebomb_state) where [
name(_, "timebomb_byte_stream", !State)
].
:- instance stream.input(timebomb_byte_stream, timebomb_state) where [].
:- instance stream.reader(timebomb_byte_stream, byte,
timebomb_state, timebomb_error)
where [
(get(_Stream, Result, !.State, unsafe_promise_unique(!:State)) :-
!.State = timebomb_state(TStream, TState0, Countdown0),
( if Countdown0 < 0 then
Result = error(already_exploded)
else if Countdown0 = 0 then
Result = error(bang),
!:State = 'new timebomb_state'(TStream, TState0, -1)
else
get(TStream, ByteResult, unsafe_promise_unique(TState0), TState),
(
ByteResult = ok(Byte),
Countdown = Countdown0 - 1,
Result = ok(Byte)
;
ByteResult = eof,
Countdown = -1,
Result = eof
;
ByteResult = error(Error),
Countdown = -1,
Result = error(stream_error(univ(Error)))
),
!:State = 'new timebomb_state'(TStream, TState, Countdown)
)
)
].
:- instance stream.bulk_reader(timebomb_byte_stream, int, bitmap,
timebomb_state, timebomb_error)
where [
(bulk_get(_, Index, NumBytes, !BM, NumBytesRead, Result,
!.State, unsafe_promise_unique(!:State)) :-
!.State = timebomb_state(TStream, TState0, Countdown0),
( if Countdown0 < 0 then
NumBytesRead = 0,
Result = error(already_exploded)
else if Countdown0 = 0 then
NumBytesRead = 0,
Result = error(bang),
!:State = 'new timebomb_state'(TStream, TState0, -1)
else
unsafe_promise_unique(TState0, TState1),
bulk_get(TStream, Index, NumBytes, !BM, NumBytesRead0,
BulkGetResult, TState1, TState),
(
BulkGetResult = ok,
( if NumBytesRead0 >= Countdown0 then
NumBytesRead = Countdown0,
Result = error(bang)
else
NumBytesRead = NumBytesRead0,
Result = ok
),
Countdown = Countdown0 - NumBytesRead
;
BulkGetResult = error(Error),
NumBytesRead = 0,
Countdown = -1,
Result = error(stream_error(univ(Error)))
),
!:State = 'new timebomb_state'(TStream, TState, Countdown)
)
)
].