aboutsummaryrefslogtreecommitdiffstats
path: root/src/cow_iolists.erl
blob: f22685dd91549b6edc6b7aed96652df48a60a78f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
%% Copyright (c) 2017, Loïc Hoguin <[email protected]>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

-module(cow_iolists).

-export([split/2]).

-ifdef(TEST).
-include_lib("proper/include/proper.hrl").
-endif.

-spec split(non_neg_integer(), iodata()) -> {iodata(), iodata()}.
split(N, Iolist) ->
	case split(N, Iolist, []) of
		{ok, Before, After} ->
			{Before, After};
		{more, _, Before} ->
			{lists:reverse(Before), <<>>}
	end.

split(0, Rest, Acc) ->
	{ok, lists:reverse(Acc), Rest};
split(N, [], Acc) ->
	{more, N, Acc};
split(N, Binary, Acc) when byte_size(Binary) =< N ->
	{ok, lists:reverse([Binary|Acc]), <<>>};
split(N, Binary, Acc) when is_binary(Binary) ->
	<< Before:N/binary, After/bits >> = Binary,
	{ok, lists:reverse([Before|Acc]), After};
split(N, [Binary|Tail], Acc) when byte_size(Binary) =< N ->
	split(N - byte_size(Binary), Tail, [Binary|Acc]);
split(N, [Binary|Tail], Acc) when is_binary(Binary) ->
	<< Before:N/binary, After/bits >> = Binary,
	{ok, lists:reverse([Before|Acc]), [After|Tail]};
split(N, [Char|Tail], Acc) when is_integer(Char) ->
	split(N - 1, Tail, [Char|Acc]);
split(N, [List|Tail], Acc0) ->
	case split(N, List, Acc0) of
		{ok, Before, After} ->
			IolistSize = iolist_size(Before),
			if
				IolistSize < N ->
					split(N - IolistSize, [After|Tail], lists:reverse(Before));
				true ->
					{ok, Before, [After|Tail]}
			end;
		{more, More, Acc} ->
			split(More, Tail, Acc)
	end.

-ifdef(TEST).

split_test_() ->
	Tests = [
		{10, "Hello world!", "Hello worl", "d!"},
		{10, <<"Hello world!">>, "Hello worl", "d!"},
		{10, ["He", [<<"llo">>], $\s, [["world"], <<"!">>]], "Hello worl", "d!"},
		{10, ["Hello "|<<"world!">>], "Hello worl", "d!"},
		{10, "Hello!", "Hello!", ""},
		{10, <<"Hello!">>, "Hello!", ""},
		{10, ["He", [<<"ll">>], $o, [["!"]]], "Hello!", ""},
		{10, ["Hel"|<<"lo!">>], "Hello!", ""},
		{10, [[<<>>|<<>>], [], <<"Hello world!">>], "Hello worl", "d!"},
		{10, [[<<"He">>|<<"llo">>], [$\s], <<"world!">>], "Hello worl", "d!"}
	],
	[{iolist_to_binary(V), fun() ->
		{B, A} = split(N, V),
		true = iolist_to_binary(RB) =:= iolist_to_binary(B),
		true = iolist_to_binary(RA) =:= iolist_to_binary(A)
	end} || {N, V, RB, RA} <- Tests].

prop_split_test() ->
	?FORALL({N, Input},
		{non_neg_integer(), iolist()},
		begin
			Size = iolist_size(Input),
			{Before, After} = split(N, Input),
			if
				N >= Size ->
					((iolist_size(After) =:= 0)
						andalso iolist_to_binary(Before) =:= iolist_to_binary(Input));
				true ->
					<<ExpectBefore:N/binary, ExpectAfter/bits>> = iolist_to_binary(Input),
					(ExpectBefore =:= iolist_to_binary(Before))
						andalso (ExpectAfter =:= iolist_to_binary(After))
			end
		end).

-endif.