Created
April 6, 2017 13:02
-
-
Save bjorng/03a869392d5a969ebf2c40044b664190 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-module(string_eqc). | |
-compile([export_all,nowarn_export_all]). | |
-include_lib("eqc/include/eqc.hrl"). | |
%%% | |
%%% Test most new functions in the new string module (PR #1330). | |
%%% | |
flat_chardata() -> | |
oneof([charlist(),unicode_binary()]). | |
nfd_chardata() -> | |
?LET(L, chardata(), decompose_chardata(L)). | |
chardata() -> | |
oneof([charlist(),deep_charlist(),unicode_binary()]). | |
charlist() -> | |
list(character()). | |
deep_charlist() -> | |
?SIZED(Size, deep_charlist(Size)). | |
deep_charlist(0) -> | |
charlist(); | |
deep_charlist(N) -> | |
list(frequency([{5,character()}, | |
{4,charlist()}, | |
{1,?LAZY(deep_charlist(N-1))}, | |
{4,unicode_binary()}])). | |
unicode_binary() -> | |
?LET(L, charlist(), unicode:characters_to_binary(L)). | |
character() -> | |
frequency([{1,choose(0, 255)}, | |
{2,choose(256, 16#D800-1)}, | |
{1,choose(16#E000, 16#10FFFF)}]). | |
primitive_character() -> | |
frequency([{1,choose($!, $~)}, | |
{1,choose(16#C0, 16#FF)}, | |
{1,choose(16#388, 16#3FF)}, | |
{1,choose(16#400, 16#45F)}]). | |
primitive_charlist() -> | |
list(primitive_character()). | |
separators(Sep) -> | |
Gen = elements(Sep), | |
frequency([{4,vector(1, Gen)}, | |
{2,vector(2, Gen)}, | |
{1,vector(3, Gen)}]). | |
disjoint_separator(S) -> | |
?SUCHTHAT(Sep, primitive_character(), not lists:member(Sep, S)). | |
mixed_guidance() -> | |
frequency([{8,{list,?SIZED(Size, mixed_guidance(Size))}}, | |
{1,list}, | |
{1,binary}]). | |
mixed_guidance(0) -> | |
leaf_type(); | |
mixed_guidance(N) -> | |
frequency([{3,?SHRINK({split,oneof([1,2,3]), | |
?LAZY({mixed_guidance(N-1), | |
mixed_guidance(N-1)})}, [list])}, | |
{1,?SHRINK(?LAZY({list,mixed_guidance(N-1)}),[list])}, | |
{5,list}, | |
{3,?SHRINK(binary, [list])}]). | |
make_mixed(binary, []) -> | |
<<>>; | |
make_mixed(binary, S) -> | |
unicode:characters_to_binary(S); | |
make_mixed(list, S) -> | |
S; | |
make_mixed({list,G}, S) -> | |
[make_mixed(G, S)]; | |
make_mixed({split,Mul,{MixLeft,MixRight}}, S) -> | |
{Left,Right} = lists:split(length(S)*Mul div 4, S), | |
[make_mixed(MixLeft, Left)|make_mixed(MixRight, Right)]. | |
%%% | |
%%% Test lexemes/2. | |
%%% | |
string_and_separator() -> | |
?LET(S0, non_empty(primitive_charlist()), | |
frequency([ | |
{1,{S0,[],[S0]}}, | |
{4,?LAZY(nonmatching_separators(S0))}, | |
{18,?LAZY(nonempty_separators(S0))}])). | |
positions(String) -> | |
?LET(Ps, list(choose(0, length(String))), | |
lists:reverse(lists:usort(Ps))). | |
nonempty_separators(S0) -> | |
?LET({SepSet,Ps}, {resize(5, non_empty(list(disjoint_separator(S0)))), | |
positions(S0)}, | |
?LET(Seps, vector(length(Ps), separators(SepSet)), | |
begin | |
S = insert_separators(Ps, S0, Seps), | |
Res = result(Ps, S0, []), | |
{S,SepSet,Res} | |
end)). | |
nonmatching_separators(S) -> | |
?LET(SepSet, non_empty(list(disjoint_separator(S))), | |
{S,SepSet,[S]}). | |
insert_separators([P|Ps], S0, [Sep|Seps]) -> | |
{A,B} = lists:split(P, S0), | |
S = A ++ Sep ++ B, | |
insert_separators(Ps, S, Seps); | |
insert_separators([], S, _Seps) -> | |
S. | |
result([P|Ps], S0, Acc) -> | |
case lists:split(P, S0) of | |
{A,[]} -> | |
result(Ps, A, Acc); | |
{A,[_|_]=B} -> | |
result(Ps, A, [B|Acc]) | |
end; | |
result([], [_|_]=S, Acc) -> | |
[S|Acc]; | |
result([], [], Acc) -> | |
Acc. | |
leaf_type() -> | |
frequency([{5,list},{3,?SHRINK(binary, [list])}]). | |
prop_lexemes() -> | |
?FORALL({{S,Sep,Res0},Guidance}, {string_and_separator(),mixed_guidance()}, | |
conjunction( | |
[{flat, | |
?LAZY(Res0 =:= string:lexemes(S, Sep))}, | |
{flat_decomposed, | |
?LAZY(decompose_list(Res0) =:= | |
string:lexemes(decompose(S), decompose_list(Sep)))}, | |
{binary, | |
?LAZY(begin | |
Bin = unicode:characters_to_binary(S), | |
Res = [unicode:characters_to_binary(E) || E <- Res0], | |
Res =:= string:lexemes(Bin, Sep) | |
end)}, | |
{mixed, | |
?LAZY(begin | |
Mixed = [make_mixed(Guidance, S)], | |
are_all_equal(string:lexemes(Mixed, Sep), Res0) | |
end)}, | |
{mixed_decomposed, | |
?LAZY( | |
begin | |
SepDecomposed = decompose_list(Sep), | |
SDecomposed = unicode:characters_to_nfd_list(S), | |
MixedDecomposed = [make_mixed(Guidance, SDecomposed)], | |
ResDecomposed = decompose_list(Res0), | |
?WHENFAIL(io:format("string:lexemes(~p, ~p) -> ~p\n", | |
[MixedDecomposed, | |
SepDecomposed, | |
ResDecomposed]), | |
are_all_equal(string:lexemes(MixedDecomposed, | |
SepDecomposed), | |
ResDecomposed)) | |
end | |
)} | |
])). | |
prop_nth_lexeme() -> | |
?FORALL({{S,Sep,Res},Guidance}, {string_and_separator(),mixed_guidance()}, | |
?FORALL(N, choose(1, length(Res)), | |
begin | |
Lexeme = lists:nth(N, Res), | |
conjunction( | |
[{flat, | |
?LAZY(Lexeme =:= string:nth_lexeme(S, N, Sep))}, | |
{binary, | |
?LAZY( | |
begin | |
Bin = unicode:characters_to_binary(S), | |
BinLexeme = unicode:characters_to_binary(Lexeme), | |
BinLexeme =:= string:nth_lexeme(Bin, N, Sep) | |
end)}, | |
{mixed, | |
?LAZY( | |
begin | |
Mixed = make_mixed(Guidance, S), | |
?WHENFAIL(io:format("~p\n", [Mixed]), | |
string:equal(Lexeme, string:nth_lexeme(Mixed, N, Sep))) | |
end)} | |
]) | |
end)). | |
decompose(L) -> | |
unicode:characters_to_nfd_list(L). | |
decompose_list(whitespace=Ws) -> | |
Ws; | |
decompose_list(L) -> | |
[case unicode:characters_to_nfd_list([C]) of | |
[C] -> C; | |
Other -> Other | |
end || C <- L]. | |
are_all_equal([H1|T1], [H2|T2]) -> | |
string:equal(H1, H2) andalso are_all_equal(T1, T2); | |
are_all_equal([], []) -> | |
true; | |
are_all_equal(_, _) -> | |
false. | |
%%% | |
%%% Test take(). | |
%%% | |
take_string() -> | |
?LET(Middle, non_empty(primitive_charlist()), | |
?LET(SepSet, non_empty(list(disjoint_separator(Middle))), | |
?LET(SepPart, list(elements(SepSet)), | |
{SepPart,Middle,SepSet}))). | |
prop_take() -> | |
?FORALL({Data,G}, {take_string(),mixed_guidance()}, | |
begin | |
Id = fun(Id) -> Id end, | |
ToBin = fun unicode:characters_to_binary/1, | |
ToMixed = fun(S) -> make_mixed(G, S) end, | |
FlatEq = fun erlang:'=:='/2, | |
BinaryEq = fun(Bin, Str) -> | |
Bin =:= unicode:characters_to_binary(Str) | |
end, | |
MixedEq = fun string:equal/2, | |
conjunction([ | |
{flat,?LAZY(do_prop_take(Data, Id, FlatEq))}, | |
{binary,?LAZY(do_prop_take(Data, ToBin, BinaryEq))}, | |
{mixed,?LAZY(do_prop_take(Data, ToMixed, MixedEq))} | |
]) | |
end). | |
do_prop_take({SepPart,Middle,_SepSet}=Data, Transform, Eq) -> | |
EmptySepData = {[],SepPart++Middle,[]}, | |
conjunction([ | |
{nonempty_separator,?LAZY(do_prop_take_0(Data, Transform, Eq))}, | |
{empty_separator,?LAZY(do_prop_take_0(EmptySepData, Transform, Eq))} | |
]). | |
do_prop_take_0({SepPart0,Middle0,SepSet0}=Data, Transform, Eq) -> | |
SepPart = unicode:characters_to_nfd_list(SepPart0), | |
Middle = unicode:characters_to_nfd_list(Middle0), | |
SepSet = decompose_list(SepSet0), | |
DecomposedData = {SepPart,Middle,SepSet}, | |
conjunction([{nfc,?LAZY(do_prop_take_1(Data, Transform, Eq))}, | |
{nfd,?LAZY(do_prop_take_1(DecomposedData, Transform, Eq))}]). | |
do_prop_take_1({SepPart,Middle,SepSet}, Transform, Eq) -> | |
Leading = Transform(SepPart ++ Middle), | |
Trailing = Transform(Middle ++ SepPart), | |
conjunction([ | |
{leading, | |
?WHENFAIL(io:format("string:take(~tp, ~tp) -> {~tp,~tp}\n", | |
[Leading,SepSet,SepPart,Middle]), | |
begin | |
{La,Lb} = string:take(Leading, SepSet), | |
Eq(La, SepPart) andalso Eq(Lb, Middle) | |
end)}, | |
{trailing, | |
?WHENFAIL(io:format("string:take(~tp, ~tp, false, trailing) ->" | |
" {~tp,~tp}\n", | |
[Trailing,SepSet,SepPart,Middle]), | |
begin | |
{Ta,Tb} = string:take(Trailing, SepSet, false, trailing), | |
Eq(Ta, Middle) andalso Eq(Tb, SepPart) | |
end)}, | |
{leading_complement, | |
?WHENFAIL(io:format("string:take(~tp, ~tp, true) -> {~tp,~tp}\n", | |
[Leading,SepSet,SepPart,Middle]), | |
begin | |
{LCa,LCb} = string:take(Trailing, SepSet, true), | |
Eq(LCa, Middle) andalso Eq(LCb, SepPart) | |
end)}, | |
{trailing_complement, | |
?WHENFAIL(io:format("string:take(~tp, ~tp, true, trailing) ->" | |
" {~tp,~tp}\n", | |
[Leading,SepSet,SepPart,Middle]), | |
begin | |
{TCa,TCb} = string:take(Leading, SepSet, true, trailing), | |
Eq(TCa, SepPart) andalso Eq(TCb, Middle) | |
end)} | |
]). | |
%%% | |
%%% Test trim(). | |
%%% | |
trim_string() -> | |
?LET(Middle, non_empty(primitive_charlist()), | |
?LET(SepSet, non_empty(list(disjoint_separator(Middle))), | |
?LET(SepPart, list(elements(SepSet)), | |
{SepPart,Middle,SepSet}))). | |
prop_trim() -> | |
?FORALL({Data,G}, {trim_string(),mixed_guidance()}, | |
begin | |
Id = fun(Id) -> Id end, | |
ToBin = fun unicode:characters_to_binary/1, | |
ToMixed = fun(S) -> make_mixed(G, S) end, | |
FlatEq = fun erlang:'=:='/2, | |
BinaryEq = fun(Bin, Str) -> | |
Bin =:= unicode:characters_to_binary(Str) | |
end, | |
MixedEq = fun string:equal/2, | |
conjunction([ | |
{flat,?LAZY(do_prop_trim(Data, Id, FlatEq))}, | |
{binary,?LAZY(do_prop_trim(Data, ToBin, BinaryEq))}, | |
{mixed,?LAZY(do_prop_trim(Data, ToMixed, MixedEq))} | |
]) | |
end). | |
do_prop_trim({SepPart,Middle,_SepSet}=Data, Transform, Eq) -> | |
EmptySepData = {[],SepPart++Middle,[]}, | |
WsData = ws_data(Data), | |
conjunction([ | |
{nonempty_separator,?LAZY(do_prop_trim_0(Data, Transform, Eq))}, | |
{empty_separator,?LAZY(do_prop_trim_0(EmptySepData, Transform, Eq))}, | |
{ws_separator,?LAZY(do_prop_trim_0(WsData, Transform, Eq))} | |
]). | |
ws_data({SepPart0,Middle,SepSet}) -> | |
Ws = "\r\n\s\t\r\n\r\n\s\t", | |
Map = map_ws(SepSet, Ws, Ws, #{}), | |
SepPart = [maps:get(C, Map) || C <- SepPart0], | |
{SepPart,Middle,whitespace}. | |
map_ws([H|T], [W|Ws], MoreWs, Map) -> | |
map_ws(T, Ws, MoreWs, Map#{H=>W}); | |
map_ws([_|_]=Seps, [], MoreWs, Map) -> | |
map_ws(Seps, MoreWs, MoreWs, Map); | |
map_ws([], _, _, Map) -> | |
Map. | |
do_prop_trim_0({SepPart0,Middle0,SepSet0}=Data, Transform, Eq) -> | |
SepPart = unicode:characters_to_nfd_list(SepPart0), | |
Middle = unicode:characters_to_nfd_list(Middle0), | |
SepSet = decompose_list(SepSet0), | |
DecomposedData = {SepPart,Middle,SepSet}, | |
conjunction([{nfc,?LAZY(do_prop_trim_1(Data, Transform, Eq))}, | |
{nfd,?LAZY(do_prop_trim_1(DecomposedData, Transform, Eq))}]). | |
do_prop_trim_1({SepPart,Middle,SepSet}, Transform, Eq) -> | |
Leading = Transform(SepPart ++ Middle), | |
Trailing = Transform(Middle ++ SepPart), | |
Both = Transform(SepPart ++ Middle ++ SepPart), | |
IsCorrect = fun(Result) -> Eq(Result, Middle) end, | |
conjunction([ | |
{leading, | |
?LAZY(apply_trim(Leading, leading, SepSet, IsCorrect))}, | |
{trailingv, | |
?LAZY(apply_trim(Trailing, trailing, SepSet, IsCorrect))}, | |
{leading, | |
?LAZY(apply_trim(Both, both, SepSet, IsCorrect))}]). | |
apply_trim(S, both, whitespace, IsCorrect) -> | |
?WHENFAIL(io:format("string:trim(~tp)\n", [S]), | |
IsCorrect(string:trim(S))); | |
apply_trim(S, Direction, whitespace, IsCorrect) -> | |
?WHENFAIL(io:format("string:trim(~tp, ~tp)\n", [S,Direction]), | |
IsCorrect(string:trim(S, Direction))); | |
apply_trim(S, Direction, Separators, IsCorrect) -> | |
?WHENFAIL(io:format("string:trim(~tp, ~tp, ~tp)\n", [S,Direction,Separators]), | |
IsCorrect(string:trim(S, Direction, Separators))). | |
%%% | |
%%% Test split(). | |
%%% | |
split_string() -> | |
?LET(S0, primitive_charlist(), | |
?LET({Sep,Positions}, {non_empty(list(disjoint_separator(S0))), | |
non_empty(positions(S0))}, | |
begin | |
S = split_insert_separators(Positions, S0, Sep), | |
Res = split_result(Positions, S0, []), | |
{S,Sep,Res} | |
end)). | |
split_insert_separators([P|Ps], S0, Sep) -> | |
{A,B} = lists:split(P, S0), | |
S = A ++ Sep ++ B, | |
split_insert_separators(Ps, S, Sep); | |
split_insert_separators([], S, _Sep) -> | |
S. | |
split_result([P|Ps], S, Acc) -> | |
{A,B} = lists:split(P, S), | |
split_result(Ps, A, [B|Acc]); | |
split_result([], S, Acc) -> | |
[S|Acc]. | |
prop_split() -> | |
?FORALL({Data,G}, {split_string(),mixed_guidance()}, | |
begin | |
Id = fun(Id) -> Id end, | |
ToBin = fun unicode:characters_to_binary/1, | |
ToMixed = fun(S) -> make_mixed(G, S) end, | |
FlatEq = fun erlang:'=:='/2, | |
BinaryEq = fun(Bin, Str) -> | |
Bin =:= unicode:characters_to_binary(Str) | |
end, | |
MixedEq = fun string:equal/2, | |
conjunction([ | |
{flat,?LAZY(do_prop_split(Data, Id, FlatEq))}, | |
{binary,?LAZY(do_prop_split(Data, ToBin, BinaryEq))}, | |
{mixed,?LAZY(do_prop_split(Data, ToMixed, MixedEq))} | |
]) | |
end). | |
do_prop_split({S0,Sep0,Res0}=Data, Transform, Eq) -> | |
S = unicode:characters_to_nfd_list(S0), | |
Sep = decompose_list(Sep0), | |
Res = decompose_list(Res0), | |
DecomposedData = {S,Sep,Res}, | |
conjunction([{nfc,?LAZY(do_prop_split_1(Data, Transform, Eq))}, | |
{nfd,?LAZY(do_prop_split_1(DecomposedData, Transform, Eq))}]). | |
do_prop_split_1({S0,Sep,Res}, Transform, Eq) -> | |
S = Transform(S0), | |
LeadingTrailing = | |
case Res of | |
[_,_,_|_] -> | |
[H|T] = Res, | |
LeadingRes = [H,lists:flatten(lists:join(Sep, T))], | |
Last = lists:last(Res), | |
AllButLast = lists:droplast(Res), | |
TrailingRes = [lists:flatten(lists:join(Sep, AllButLast)),Last], | |
[{leading,?LAZY(apply_split(S, Sep, leading, LeadingRes, Eq))}, | |
{trailing,?LAZY(apply_split(S, Sep, trailing, TrailingRes, Eq))}]; | |
_ -> | |
[] | |
end, | |
conjunction([{all,?LAZY(apply_split(S, Sep, all, Res, Eq))}|LeadingTrailing]). | |
apply_split(S, Sep, Direction, Res, Eq) -> | |
?WHENFAIL(io:format("string:split(~tp, ~tp, ~tp) ->\n ~p\n", [S,Sep,Direction,Res]), | |
split_all_equal(string:split(S, Sep, Direction), Res, Eq)). | |
split_all_equal([H1|T1], [H2|T2], Eq) -> | |
Eq(H1, H2) andalso split_all_equal(T1, T2, Eq); | |
split_all_equal([], [], _) -> | |
true; | |
split_all_equal(_, _, _) -> | |
false. | |
%%% | |
%%% Test replace(). | |
%%% | |
replace_string() -> | |
?LET({S0,Repl}, {primitive_charlist(),primitive_charlist()}, | |
?LET({Pat,Positions}, {non_empty(list(disjoint_separator(S0))), | |
non_empty(positions(S0))}, | |
begin | |
S = split_insert_separators(Positions, S0, Pat), | |
Res = split_result(Positions, S0, []), | |
{S,Pat,Repl,Res} | |
end)). | |
prop_replace() -> | |
?FORALL({Data,G}, {replace_string(),mixed_guidance()}, | |
begin | |
Id = fun(Id) -> Id end, | |
ToBin = fun unicode:characters_to_binary/1, | |
ToMixed = fun(S) -> make_mixed(G, S) end, | |
conjunction([ | |
{flat,?LAZY(do_prop_replace(Data, Id))}, | |
{binary,?LAZY(do_prop_replace(Data, ToBin))}, | |
{mixed,?LAZY(do_prop_replace(Data, ToMixed))} | |
]) | |
end). | |
do_prop_replace({S0,Pat0,Repl0,Res0}=Data, Transform) -> | |
S = unicode:characters_to_nfd_list(S0), | |
Repl = unicode:characters_to_nfd_list(Repl0), | |
Pat = decompose_list(Pat0), | |
Res = decompose_list(Res0), | |
DecomposedData = {S,Pat,Repl,Res}, | |
conjunction([{nfc,?LAZY(do_prop_replace_1(Data, Transform))}, | |
{nfd,?LAZY(do_prop_replace_1(DecomposedData, Transform))}]). | |
do_prop_replace_1({S0,Pat,Repl0,Res}, Transform) -> | |
S = Transform(S0), | |
Repl = Transform(Repl0), | |
ResAll = lists:join(Repl0, Res), | |
LeadingTrailing = | |
case Res of | |
[_,_,_|_] -> | |
[H|T] = Res, | |
LeadingRes = [H,Repl0,lists:join(Pat, T)], | |
Last = lists:last(Res), | |
AllButLast = lists:droplast(Res), | |
TrailingRes = [lists:join(Pat, AllButLast),Repl0,Last], | |
[{leading,?LAZY(apply_replace(S, Pat, Repl, leading, LeadingRes))}, | |
{trailing,?LAZY(apply_replace(S, Pat, Repl, trailing, TrailingRes))} | |
]; | |
_ -> | |
[] | |
end, | |
conjunction([{all,?LAZY(apply_replace(S, Pat, Repl, all, ResAll))}|LeadingTrailing]). | |
apply_replace(S, Pat, Repl, Direction, Res) -> | |
?WHENFAIL(io:format("string:replace(~tp, ~tp, ~tp, ~tp) ->\n ~p\n", | |
[S,Pat,Repl,Direction,Res]), | |
string:equal(string:replace(S, Pat, Repl, Direction), Res)). | |
%%% | |
%%% Test slice(). | |
%%% | |
slice_string() -> | |
?LET(S, primitive_charlist(), | |
begin | |
L = length(S), | |
?LET(Pos, choose(0, L), | |
?LET(Len, choose(0, L-Pos), | |
begin | |
Res = lists:sublist(S, Pos+1, Len), | |
{S,Pos,Len,Res} | |
end)) | |
end). | |
prop_slice() -> | |
?FORALL({Data,G}, {slice_string(),mixed_guidance()}, | |
begin | |
Id = fun(Id) -> Id end, | |
ToBin = fun unicode:characters_to_binary/1, | |
ToMixed = fun(S) -> make_mixed(G, S) end, | |
FlatEq = fun erlang:'=:='/2, | |
BinaryEq = fun(Bin, Str) -> | |
Bin =:= unicode:characters_to_binary(Str) | |
end, | |
MixedEq = fun string:equal/2, | |
conjunction([ | |
{flat,?LAZY(do_prop_slice(Data, Id, FlatEq))}, | |
{binary,?LAZY(do_prop_slice(Data, ToBin, BinaryEq))}, | |
{mixed,?LAZY(do_prop_slice(Data, ToMixed, MixedEq))} | |
]) | |
end). | |
do_prop_slice({S0,Pos,Len,Res0}=Data, Transform, Eq) -> | |
S = unicode:characters_to_nfd_list(S0), | |
Res = unicode:characters_to_nfd_list(Res0), | |
DecomposedData = {S,Pos,Len,Res}, | |
conjunction([ | |
{nfc,?LAZY(do_prop_slice_1(Data, Transform, Eq))}, | |
{nfd,?LAZY(do_prop_slice_1(DecomposedData, Transform, Eq))} | |
]). | |
do_prop_slice_1({S0,Pos,Len,Res}, Transform, Eq) -> | |
S = Transform(S0), | |
apply_slice(S, Pos, Len, Res, Eq). | |
apply_slice(S, Pos, Len, Res, Eq) -> | |
?WHENFAIL(io:format("string:slice(~tp, ~tp, ~tp)\n", [S,Pos,Len]), | |
Eq(string:slice(S, Pos, Len), Res)). | |
%%% | |
%%% Test prefix(). | |
%%% | |
prefix_string() -> | |
?LET(S, primitive_charlist(), | |
?LET(Other, non_empty(list(disjoint_separator(S))), | |
{S,Other})). | |
prop_prefix() -> | |
?FORALL({Data,G}, {prefix_string(),mixed_guidance()}, | |
begin | |
Id = fun(Id) -> Id end, | |
ToBin = fun unicode:characters_to_binary/1, | |
ToMixed = fun(S) -> make_mixed(G, S) end, | |
FlatEq = fun erlang:'=:='/2, | |
BinaryEq = fun(Bin, Str) -> | |
Bin =:= unicode:characters_to_binary(Str) | |
end, | |
MixedEq = fun string:equal/2, | |
conjunction([ | |
{flat,?LAZY(do_prop_prefix(Data, Id, FlatEq))}, | |
{binary,?LAZY(do_prop_prefix(Data, ToBin, BinaryEq))}, | |
{mixed,?LAZY(do_prop_prefix(Data, ToMixed, MixedEq))} | |
]) | |
end). | |
do_prop_prefix({S0,Other0}=Data, Transform, Eq) -> | |
S = unicode:characters_to_nfd_list(S0), | |
Other = unicode:characters_to_nfd_list(Other0), | |
DecomposedData = {S,Other}, | |
conjunction([ | |
{nfc,?LAZY(do_prop_prefix_1(Data, Transform, Eq))}, | |
{nfd,?LAZY(do_prop_prefix_1(DecomposedData, Transform, Eq))} | |
]). | |
do_prop_prefix_1({S0,Other0}, Transform, Eq) -> | |
S = Transform(S0), | |
Other = Transform(Other0), | |
Str = Transform(Other0 ++ S0), | |
conjunction([ | |
{match,?LAZY(apply_prefix(Str, Other, Eq, S))}, | |
{nomatch,?LAZY(apply_prefix(S, Other, fun erlang:'=:='/2, nomatch))} | |
]). | |
apply_prefix(Str, Other, Eq, Res) -> | |
?WHENFAIL(io:format("string:prefix(~tp, ~tp)\n", [Str,Other]), | |
Eq(string:prefix(Str, Other), Res)). | |
%%% | |
%%% Test find(). | |
%%% | |
find_string() -> | |
?LET(S0, primitive_charlist(), | |
?LET({Pat,Pos}, {non_empty(list(disjoint_separator(S0))), | |
choose(0, length(S0))}, | |
{lists:split(Pos, S0),Pat})). | |
prop_find() -> | |
?FORALL({Data,G}, {find_string(),mixed_guidance()}, | |
begin | |
Id = fun(Id) -> Id end, | |
ToBin = fun unicode:characters_to_binary/1, | |
ToMixed = fun(S) -> make_mixed(G, S) end, | |
FlatEq = fun erlang:'=:='/2, | |
BinaryEq = fun(Bin, Str) -> | |
Bin =:= unicode:characters_to_binary(Str) | |
end, | |
MixedEq = fun string:equal/2, | |
conjunction([ | |
{flat,?LAZY(do_prop_find(Data, Id, FlatEq))}, | |
{binary,?LAZY(do_prop_find(Data, ToBin, BinaryEq))}, | |
{mixed,?LAZY(do_prop_find(Data, ToMixed, MixedEq))} | |
]) | |
end). | |
do_prop_find({{L0,R0},Pat0}=Data, Transform, Eq) -> | |
L = unicode:characters_to_nfd_list(L0), | |
R = unicode:characters_to_nfd_list(R0), | |
Pat = unicode:characters_to_nfd_list(Pat0), | |
DecomposedData = {{L,R},Pat}, | |
conjunction([ | |
{nfc,?LAZY(do_prop_find_1(Data, Transform, Eq))}, | |
{nfd,?LAZY(do_prop_find_1(DecomposedData, Transform, Eq))} | |
]). | |
do_prop_find_1({{L,R},Pat0}, Transform, Eq) -> | |
S1 = Transform(L ++ Pat0 ++ R), | |
Pat = Transform(Pat0), | |
conjunction([{leading1, | |
?LAZY(apply_find(S1, Pat, leading, Eq, Pat0 ++ R))}, | |
{leading2, | |
?LAZY(begin | |
S = Transform(L ++ Pat0 ++ R ++ Pat0), | |
apply_find(S, Pat, leading, Eq, Pat0 ++ R ++ Pat0) | |
end)}, | |
{trailing1, | |
?LAZY(apply_find(S1, Pat, trailing, Eq, Pat0 ++ R))}, | |
{trailing2, | |
?LAZY(begin | |
S = Transform(Pat0 ++ L ++ Pat0 ++ R), | |
apply_find(S, Pat, trailing, Eq, Pat0 ++ R) | |
end)}, | |
{leading_nomatch, | |
?LAZY(begin | |
S = Transform(L ++ R), | |
apply_find(S, Pat, leading, fun erlang:'=:='/2, nomatch) | |
end)}, | |
{trailing_nomatch, | |
?LAZY(begin | |
S = Transform(L ++ R), | |
apply_find(S, Pat, trailing, fun erlang:'=:='/2, nomatch) | |
end)} | |
]). | |
apply_find(S, Pat, Dir, Eq, Res) -> | |
?WHENFAIL(io:format("string:find(~tp, ~tp, ~tp) -> ~p\n", [S,Pat,Dir,Res]), | |
Eq(string:find(S, Pat, Dir), Res)). | |
%%% | |
%%% Test chomp(). | |
%%% | |
chomp_string() -> | |
MainPart = list(frequency([{10,primitive_character()}, | |
{1,$\r}, | |
{1,$\n}, | |
{1,"\r\n"}])), | |
Ending = list(oneof([$\r,$\n,"\r\n"])), | |
?LET(S0, [MainPart,Ending], | |
begin | |
S = lists:flatten(S0), | |
Res = chomp_result(S), | |
{S,Res} | |
end). | |
chomp_result(S0) -> | |
S1 = lists:reverse(S0), | |
lists:reverse(chomp_result_1(S1)). | |
chomp_result_1([$\n,$\r|T]) -> | |
chomp_result_1(T); | |
chomp_result_1([$\n|T]) -> | |
chomp_result_1(T); | |
chomp_result_1(T) -> | |
T. | |
prop_chomp() -> | |
?FORALL({Data,G}, {chomp_string(),mixed_guidance()}, | |
begin | |
Id = fun(Id) -> Id end, | |
ToBin = fun unicode:characters_to_binary/1, | |
ToMixed = fun(S) -> make_mixed(G, S) end, | |
FlatEq = fun erlang:'=:='/2, | |
BinaryEq = fun(Bin, Str) -> | |
Bin =:= unicode:characters_to_binary(Str) | |
end, | |
MixedEq = fun string:equal/2, | |
conjunction([ | |
{flat,?LAZY(do_prop_chomp(Data, Id, FlatEq))}, | |
{binary,?LAZY(do_prop_chomp(Data, ToBin, BinaryEq))}, | |
{mixed,?LAZY(do_prop_chomp(Data, ToMixed, MixedEq))} | |
]) | |
end). | |
do_prop_chomp({S0,Res}, Transform, Eq) -> | |
S = Transform(S0), | |
?WHENFAIL(io:format("string:chomp(~tp)\n", [S]), | |
Eq(string:chomp(S), Res)). | |
%%% | |
%%% Test equal(). | |
%%% | |
prop_equal() -> | |
?FORALL({Data,G}, {charlist(),mixed_guidance()}, | |
begin | |
Id = fun(Id) -> Id end, | |
ToBin = fun unicode:characters_to_binary/1, | |
ToMixed = fun(S) -> make_mixed(G, S) end, | |
conjunction([ | |
{flat,?LAZY(do_prop_equal(Data, Id))}, | |
{binary,?LAZY(do_prop_equal(Data, ToBin))}, | |
{mixed,?LAZY(do_prop_equal(Data, ToMixed))} | |
]) | |
end). | |
do_prop_equal(S0, Transform) -> | |
S = Transform(S0), | |
conjunction([ | |
{plain,?LAZY(apply_equal(S, S0, false, none))}, | |
{casefold, | |
?LAZY(begin | |
Casefolded = string:casefold(S0), | |
apply_equal(S, Casefolded, true, none) | |
end)}, | |
{nfd, | |
?LAZY(begin | |
Decomposed = unicode:characters_to_nfd_list(S0), | |
apply_equal(S, Decomposed, false, nfd) | |
end)} | |
]). | |
apply_equal(A, B, IgnoreCase, Norm) -> | |
?WHENFAIL(io:format("string:equal(~tp, ~tp, ~tp, ~tp)\n", | |
[A,B,IgnoreCase,Norm]), | |
string:equal(A, B, IgnoreCase, Norm) andalso | |
string:equal(B, A, IgnoreCase, Norm)). | |
%%% | |
%%% Test pad(). | |
%%% | |
pad_string() -> | |
{charlist(),nat()}. | |
prop_pad() -> | |
?FORALL({Data,G}, {pad_string(),mixed_guidance()}, | |
begin | |
Id = fun(Id) -> Id end, | |
ToBin = fun unicode:characters_to_binary/1, | |
ToMixed = fun(S) -> make_mixed(G, S) end, | |
conjunction([ | |
{flat,?LAZY(do_prop_pad(Data, Id))}, | |
{binary,?LAZY(do_prop_pad(Data, ToBin))}, | |
{mixed,?LAZY(do_prop_pad(Data, ToMixed))} | |
]) | |
end). | |
do_prop_pad(Data, Transform) -> | |
conjunction([ | |
{space,?LAZY(do_prop_pad_1(Data, Transform, $\s))}, | |
{x,?LAZY(do_prop_pad_1(Data, Transform, $x))}]). | |
do_prop_pad_1({S0,PadLen}, Transform, PadChar) -> | |
S = Transform(S0), | |
StrLen = string:length(S0), | |
Length = StrLen + PadLen, | |
PadString = lists:duplicate(PadLen, PadChar), | |
conjunction([ | |
{trailing, | |
?LAZY(begin | |
Res = S0 ++ PadString, | |
apply_pad(S, Length, trailing, PadChar, Res) | |
end)}, | |
{leading, | |
?LAZY(begin | |
Res = PadString ++ S0, | |
apply_pad(S, Length, leading, PadChar, Res) | |
end)}, | |
{both, | |
?LAZY(begin | |
LeftPadString = lists:duplicate(PadLen div 2, PadChar), | |
RightPadString = lists:duplicate(PadLen div 2 + | |
PadLen rem 2, | |
PadChar), | |
Res = LeftPadString ++ S0 ++ RightPadString, | |
apply_pad(S, Length, both, PadChar, Res) | |
end)} | |
]). | |
apply_pad(S, Length, Dir, Char, Res) -> | |
?WHENFAIL(io:format("string:pad(~tp, ~tp, ~tp, ~tp)\n", | |
[S,Length,Dir,Char]), | |
string:equal(string:pad(S, Length, Dir, Char), Res)). | |
%%% | |
%%% Other properties. | |
%%% | |
prop_graphemes_length() -> | |
?FORALL(S, nfd_chardata(), | |
begin | |
Graphemes = string:to_graphemes(S), | |
string:length(Graphemes) =:= string:length(S) | |
end). | |
prop_graphemes() -> | |
?FORALL(S, nfd_chardata(), | |
begin | |
Graphemes = string:to_graphemes(S), | |
get_graphemes(S) =:= Graphemes | |
end). | |
prop_is_empty() -> | |
?FORALL(S, chardata(), | |
begin | |
Bin = if is_binary(S) -> S; | |
true -> unicode:characters_to_binary(S) | |
end, | |
IsEmpty = Bin =:= <<>>, | |
IsEmpty =:= string:is_empty(S) | |
end). | |
prop_idempotent_casefold() -> | |
idempotent(fun string:casefold/1). | |
prop_idempotent_lowercase() -> | |
idempotent(fun string:lowercase/1). | |
prop_idempotent_titlecase() -> | |
idempotent(fun string:titlecase/1). | |
prop_idempotent_uppercase() -> | |
idempotent(fun string:uppercase/1). | |
prop_deep_casefold() -> | |
deep(casefold). | |
prop_deep_lowercase() -> | |
deep(lowercase). | |
prop_deep_titlecase() -> | |
deep(titlecase). | |
prop_deep_uppercase() -> | |
deep(uppercase). | |
prop_next_codepoint() -> | |
?FORALL(S0, chardata(), | |
begin | |
S = do_prop_next_codepoint(S0), | |
string:equal(S0, S) | |
end). | |
do_prop_next_codepoint(S) -> | |
case string:next_codepoint(S) of | |
[C|T] -> | |
[C|do_prop_next_codepoint(T)]; | |
[] -> | |
[] | |
end. | |
idempotent(F) -> | |
?FORALL(S, chardata(), | |
begin | |
Res = F(S), | |
Res =:= F(Res) | |
end). | |
deep(F) -> | |
?FORALL({S,G}, {charlist(),mixed_guidance()}, | |
begin | |
Mixed = make_mixed(G, S), | |
?WHENFAIL(io:format("string:~p(~tp)\n", [F,Mixed]), | |
string:equal(string:F(S), string:F(Mixed))) | |
end). | |
flatten(Bin) when is_binary(Bin) -> | |
Bin; | |
flatten(L) -> | |
unicode:characters_to_list(L). | |
decompose_chardata(Bin) when is_binary(Bin) -> | |
unicode:characters_to_nfd_binary(Bin); | |
decompose_chardata([H|T]) when is_integer(H) -> | |
case unicode:characters_to_nfd_list([H]) of | |
[H] -> | |
[H|decompose_chardata(T)]; | |
[_|_]=Chars -> | |
decompose_chardata(Chars ++ T) | |
end; | |
decompose_chardata([H|T]) -> | |
[decompose_chardata(H)|decompose_chardata(T)]; | |
decompose_chardata([]) -> | |
[]. | |
get_graphemes(S) -> | |
case string:next_grapheme(S) of | |
[C|T] -> | |
[C|get_graphemes(T)]; | |
[] -> | |
[] | |
end. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment