Packages

Property-based testing and metamorphic testing combinator library for Gleam

Current section

Files

Jump to
metamon src metamon@generator.erl
Raw

src/metamon@generator.erl

-module(metamon@generator).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/metamon/generator.gleam").
-export([generate/3, edges_of/1, sample/2, statistics/3, no_edges/1, return/1, map/2, sized/1, resize/2, scale/2, recursive/2, int/1, non_negative_int/0, positive_int/0, negative_int/0, byte/0, float/2, list_of/2, bit_array/1, bit_array_printable/1, set_of/2, ascii_lower/0, ascii_upper/0, ascii_digit/0, ascii_printable/0, with_examples/2, add_edges/2, bind/2, map2/3, map3/4, map4/5, map5/6, map6/7, tuple2/2, tuple3/3, tuple4/4, tuple5/5, one_of/1, element_of/1, frequency/1, bool/0, ascii_letter/0, ascii_alphanumeric/0, unicode_codepoint/0, dict_of/3, option_of/1, result_of/2, filter/2, non_empty_list_of/2, string/2, string_ascii/1, string_alpha/1, string_alphanumeric/1, string_digit/1, string_printable_ascii/1, string_unicode/1, bit_array_utf8/1]).
-export_type([generator/1]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Generators: deterministic, size-aware samplers that produce values\n"
" alongside their lazy shrink alternatives.\n"
"\n"
" A `Generator(a)` is a pair of:\n"
" * `run`: takes a seed and a size and returns a `Tree(a)`\n"
" * `edges`: a finite list of must-try boundary values\n"
"\n"
" Combinators (`map`, `bind`, `map2`, `tuple2`, `one_of`, ...) preserve\n"
" integrated shrinking via the underlying `Tree(a)` and propagate edges\n"
" in a way appropriate for each combinator (see § 4.4 of the spec).\n"
).
-opaque generator(EPE) :: {generator,
fun((metamon@generator@seed:seed(), integer()) -> metamon@generator@tree:tree(EPE)),
list(EPE)}.
-file("src/metamon/generator.gleam", 36).
?DOC(
" Run the generator at the given seed and size. Used by the runner;\n"
" most user code should not call this directly.\n"
).
-spec generate(generator(EPF), metamon@generator@seed:seed(), integer()) -> metamon@generator@tree:tree(EPF).
generate(G, S, Size) ->
(erlang:element(2, G))(S, Size).
-file("src/metamon/generator.gleam", 41).
?DOC(" Inspect the must-try edge values associated with a generator.\n").
-spec edges_of(generator(EPI)) -> list(EPI).
edges_of(G) ->
erlang:element(3, G).
-file("src/metamon/generator.gleam", 51).
-spec sample_loop(
generator(EPO),
metamon@generator@seed:seed(),
integer(),
integer(),
list(EPO)
) -> list(EPO).
sample_loop(G, S, Remaining, Size, Acc) ->
case Remaining =< 0 of
true ->
lists:reverse(Acc);
false ->
{Left, Right} = metamon@generator@seed:split(S),
Value = erlang:element(2, (erlang:element(2, G))(Left, Size)),
sample_loop(G, Right, Remaining - 1, Size, [Value | Acc])
end.
-file("src/metamon/generator.gleam", 47).
?DOC(
" Pull `n` plain sample values, ignoring shrink trees. Useful at the\n"
" REPL or in test diagnostics.\n"
).
-spec sample(generator(EPL), integer()) -> list(EPL).
sample(G, N) ->
sample_loop(G, metamon@generator@seed:seed(0), N, 50, []).
-file("src/metamon/generator.gleam", 70).
?DOC(
" Bucket `n` generated values via `classify` for distribution sanity\n"
" checks.\n"
).
-spec statistics(generator(EPS), integer(), fun((EPS) -> binary())) -> gleam@dict:dict(binary(), integer()).
statistics(G, N, Classify) ->
_pipe = sample(G, N),
gleam@list:fold(
_pipe,
maps:new(),
fun(Acc, Value) ->
Label = Classify(Value),
gleam@dict:upsert(Acc, Label, fun(Existing) -> case Existing of
{some, Count} ->
Count + 1;
none ->
1
end end)
end
).
-file("src/metamon/generator.gleam", 103).
?DOC(
" Strip the edges from a generator. Useful when composing into a\n"
" product type that has its own edge story.\n"
).
-spec no_edges(generator(EQE)) -> generator(EQE).
no_edges(G) ->
{generator, erlang:element(2, G), []}.
-file("src/metamon/generator.gleam", 110).
?DOC(" A constant generator that always returns `value` and has no shrinks.\n").
-spec return(EQH) -> generator(EQH).
return(Value) ->
{generator,
fun(_, _) -> metamon@generator@tree:singleton(Value) end,
[Value]}.
-file("src/metamon/generator.gleam", 117).
?DOC(" Functor map. Edges are mapped element-wise.\n").
-spec map(generator(EQJ), fun((EQJ) -> EQL)) -> generator(EQL).
map(G, F) ->
{generator,
fun(S, Size) ->
metamon@generator@tree:map((erlang:element(2, G))(S, Size), F)
end,
gleam@list:map(erlang:element(3, G), F)}.
-file("src/metamon/generator.gleam", 304).
-spec pick_by_weight(
list({integer(), generator(EUH)}),
integer(),
generator(EUH)
) -> generator(EUH).
pick_by_weight(Pairs, Roll, Fallback) ->
case Pairs of
[] ->
Fallback;
[{W, G} | Rest] ->
case Roll < W of
true ->
G;
false ->
pick_by_weight(Rest, Roll - W, Fallback)
end
end.
-file("src/metamon/generator.gleam", 322).
?DOC(" Construct a generator whose strategy depends on the current size.\n").
-spec sized(fun((integer()) -> generator(EUM))) -> generator(EUM).
sized(Builder) ->
{generator,
fun(S, Size) ->
Inner = Builder(Size),
(erlang:element(2, Inner))(S, Size)
end,
erlang:element(3, Builder(0))}.
-file("src/metamon/generator.gleam", 333).
?DOC(" Override the size used by a generator.\n").
-spec resize(generator(EUP), integer()) -> generator(EUP).
resize(G, New_size) ->
{generator,
fun(S, _) -> (erlang:element(2, G))(S, New_size) end,
erlang:element(3, G)}.
-file("src/metamon/generator.gleam", 338).
?DOC(" Transform the size given to a generator.\n").
-spec scale(generator(EUS), fun((integer()) -> integer())) -> generator(EUS).
scale(G, F) ->
{generator,
fun(S, Size) -> (erlang:element(2, G))(S, F(Size)) end,
erlang:element(3, G)}.
-file("src/metamon/generator.gleam", 361).
-spec filter_run(
generator(EUY),
fun((EUY) -> boolean()),
metamon@generator@seed:seed(),
integer(),
integer()
) -> metamon@generator@tree:tree(EUY).
filter_run(G, Predicate, S, Size, Retries_left) ->
Candidate = (erlang:element(2, G))(S, Size),
case Predicate(erlang:element(2, Candidate)) of
true ->
metamon@generator@tree:filter(Candidate, Predicate);
false ->
case Retries_left =< 0 of
true ->
erlang:error(#{gleam_error => panic,
message => <<"metamon.filter: predicate rejected the configured number of candidates in a row; the predicate is too strict"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"metamon/generator"/utf8>>,
function => <<"filter_run"/utf8>>,
line => 374});
false ->
{_, S2} = metamon@generator@seed:split(S),
filter_run(G, Predicate, S2, Size, Retries_left - 1)
end
end.
-file("src/metamon/generator.gleam", 387).
?DOC(
" Recursive generator. At `size = 0` only `base` is used; at higher\n"
" sizes the recursive call is the result of `step` applied to a copy\n"
" of itself with halved size.\n"
).
-spec recursive(generator(EVB), fun((generator(EVB)) -> generator(EVB))) -> generator(EVB).
recursive(Base, Step) ->
sized(fun(Size) -> case Size =< 0 of
true ->
Base;
false ->
Smaller = begin
_pipe = recursive(Base, Step),
resize(_pipe, Size div 2)
end,
Step(Smaller)
end end).
-file("src/metamon/generator.gleam", 421).
-spec extreme_lo(metamon@generator@range:range()) -> integer().
extreme_lo(R) ->
{Lo, _} = metamon@generator@range:bounds(R, 99, 99),
Lo.
-file("src/metamon/generator.gleam", 426).
-spec extreme_hi(metamon@generator@range:range()) -> integer().
extreme_hi(R) ->
{_, Hi} = metamon@generator@range:bounds(R, 99, 99),
Hi.
-file("src/metamon/generator.gleam", 481).
-spec string_to_utf8_bit_array(binary()) -> bitstring().
string_to_utf8_bit_array(S) ->
<<S/binary>>.
-file("src/metamon/generator.gleam", 485).
-spec bytes_to_bit_array(list(integer())) -> bitstring().
bytes_to_bit_array(Bytes) ->
gleam@list:fold(Bytes, <<>>, fun(Acc, B) -> <<Acc/bitstring, B:8>> end).
-file("src/metamon/generator.gleam", 489).
-spec attach_known_root(
metamon@generator@tree:tree(integer()),
integer(),
list(integer())
) -> metamon@generator@tree:tree(integer()).
attach_known_root(Unfolded, Expected_root, _) ->
case erlang:element(2, Unfolded) =:= Expected_root of
true ->
Unfolded;
false ->
metamon@generator@tree:singleton(Expected_root)
end.
-file("src/metamon/generator.gleam", 407).
?DOC(
" Integer in the given range, with binary shrinking toward the range\n"
" origin. Standard edges (`0`, `1`, `-1`, bounds) are populated within\n"
" the constant interval.\n"
).
-spec int(metamon@generator@range:range()) -> generator(integer()).
int(Range_value) ->
Origin = metamon@generator@range:origin(Range_value),
{generator,
fun(S, Size) ->
{Lo, Hi} = metamon@generator@range:bounds(Range_value, Size, 99),
{Value, _} = metamon@generator@seed:next_int_in(S, Lo, Hi),
Shrinks = metamon@generator@shrink:int_toward(Origin, Value),
_pipe = metamon@generator@tree:unfold(
Value,
fun(V) -> metamon@generator@shrink:int_toward(Origin, V) end
),
attach_known_root(_pipe, Value, Shrinks)
end,
metamon@generator@edges:ints_in(
extreme_lo(Range_value),
extreme_hi(Range_value)
)}.
-file("src/metamon/generator.gleam", 440).
?DOC(
" Integer in `[0, 1_000_000]`. Linear scaling so small values come\n"
" first; shrinks toward 0.\n"
).
-spec non_negative_int() -> generator(integer()).
non_negative_int() ->
int(metamon@generator@range:linear(0, 1000000)).
-file("src/metamon/generator.gleam", 445).
?DOC(" Integer in `[1, 1_000_000]`. Shrinks toward 1.\n").
-spec positive_int() -> generator(integer()).
positive_int() ->
int(metamon@generator@range:linear_from(1, 1, 1000000)).
-file("src/metamon/generator.gleam", 450).
?DOC(" Integer in `[-1_000_000, -1]`. Shrinks toward -1.\n").
-spec negative_int() -> generator(integer()).
negative_int() ->
int(metamon@generator@range:linear_from(-1, -1000000, -1)).
-file("src/metamon/generator.gleam", 455).
?DOC(" A single byte (`[0, 255]`).\n").
-spec byte() -> generator(integer()).
byte() ->
int(metamon@generator@range:constant(0, 255)).
-file("src/metamon/generator.gleam", 504).
?DOC(
" Float in the closed interval `[lo, hi]`. Shrinking moves toward the\n"
" closest endpoint (no fancy mantissa shrinking yet).\n"
).
-spec float(float(), float()) -> generator(float()).
float(Lo, Hi) ->
{Low, High} = case Lo > Hi of
true ->
{Hi, Lo};
false ->
{Lo, Hi}
end,
{generator,
fun(S, _) ->
{Scaled, _} = metamon@generator@seed:next_int_in(S, 0, 1000000),
Fraction = erlang:float(Scaled) / 1000000.0,
Value = Low + ((High - Low) * Fraction),
metamon@generator@tree:from_list(
Value,
[metamon@generator@tree:singleton(Low),
metamon@generator@tree:singleton(High)]
)
end,
metamon@generator@edges:floats_in(Low, High)}.
-file("src/metamon/generator.gleam", 627).
-spec codepoint_to_string(integer()) -> binary().
codepoint_to_string(Codepoint) ->
metamon_ffi:codepoint_to_string(Codepoint).
-file("src/metamon/generator.gleam", 675).
-spec prepend_to_shrinks(
metamon@generator@tree:shrinks(metamon@generator@tree:tree(EWY)),
list(metamon@generator@tree:tree(EWY))
) -> metamon@generator@tree:shrinks(metamon@generator@tree:tree(EWY)).
prepend_to_shrinks(Existing, Prepended) ->
metamon@generator@tree:append_shrinks(
metamon@generator@tree:shrinks_from_list(Prepended),
Existing
).
-file("src/metamon/generator.gleam", 668).
-spec attach_drop_shrinks(metamon@generator@tree:tree(list(EWT))) -> metamon@generator@tree:tree(list(EWT)).
attach_drop_shrinks(T) ->
Drops = begin
_pipe = metamon@generator@shrink:list_drops(erlang:element(2, T)),
gleam@list:map(_pipe, fun metamon@generator@tree:singleton/1)
end,
{tree,
erlang:element(2, T),
prepend_to_shrinks(erlang:element(3, T), Drops)}.
-file("src/metamon/generator.gleam", 648).
-spec generate_list_tree(
generator(EWP),
metamon@generator@seed:seed(),
integer(),
integer()
) -> metamon@generator@tree:tree(list(EWP)).
generate_list_tree(Element, S, Size, Length) ->
case Length =< 0 of
true ->
metamon@generator@tree:singleton([]);
false ->
{S_head, S_tail} = metamon@generator@seed:split(S),
Head_tree = (erlang:element(2, Element))(S_head, Size),
Tail_tree = generate_list_tree(Element, S_tail, Size, Length - 1),
Combined = begin
_pipe = metamon@generator@tree:zip(Head_tree, Tail_tree),
metamon@generator@tree:map(
_pipe,
fun(Pair) ->
[erlang:element(1, Pair) | erlang:element(2, Pair)]
end
)
end,
attach_drop_shrinks(Combined)
end.
-file("src/metamon/generator.gleam", 682).
-spec list_edges_in_range(list(EXF), integer(), integer()) -> list(list(EXF)).
list_edges_in_range(Element_edges, Lo_len, Hi_len) ->
Candidates = case Element_edges of
[] ->
[[]];
[First | _] ->
[[], [First], Element_edges]
end,
gleam@list:filter(
Candidates,
fun(Xs) ->
Length = erlang:length(Xs),
(Length >= Lo_len) andalso (Length =< Hi_len)
end
).
-file("src/metamon/generator.gleam", 635).
?DOC(
" A list of values whose length is drawn from `len`. Shrinking removes\n"
" elements via `shrink/list_drops` and shrinks each remaining element.\n"
).
-spec list_of(generator(EWL), metamon@generator@range:range()) -> generator(list(EWL)).
list_of(Element, Len) ->
{Lo_len, Hi_len} = metamon@generator@range:bounds(Len, 99, 99),
{generator,
fun(S, Size) ->
{S_len, S_items} = metamon@generator@seed:split(S),
{Lo, Hi} = metamon@generator@range:bounds(Len, Size, 99),
{Length, _} = metamon@generator@seed:next_int_in(S_len, Lo, Hi),
generate_list_tree(Element, S_items, Size, Length)
end,
list_edges_in_range(erlang:element(3, Element), Lo_len, Hi_len)}.
-file("src/metamon/generator.gleam", 461).
?DOC(
" Bit array of byte-length within `len`. Each byte is generated\n"
" uniformly.\n"
).
-spec bit_array(metamon@generator@range:range()) -> generator(bitstring()).
bit_array(Len) ->
_pipe = list_of(byte(), Len),
map(_pipe, fun bytes_to_bit_array/1).
-file("src/metamon/generator.gleam", 469).
?DOC(
" Bit array whose every byte is a printable ASCII codepoint\n"
" (`0x20`..`0x7E`). Useful for fuzzing parsers that take `BitArray`\n"
" but expect printable input (HTTP headers, MIME types, etc.).\n"
).
-spec bit_array_printable(metamon@generator@range:range()) -> generator(bitstring()).
bit_array_printable(Len) ->
_pipe = list_of(int(metamon@generator@range:constant(32, 126)), Len),
map(_pipe, fun bytes_to_bit_array/1).
-file("src/metamon/generator.gleam", 724).
?DOC(" Generate a `set.Set(a)` of size in `len`.\n").
-spec set_of(generator(EXU), metamon@generator@range:range()) -> generator(gleam@set:set(EXU)).
set_of(Element, Len) ->
_pipe = list_of(Element, Len),
map(_pipe, fun gleam@set:from_list/1).
-file("src/metamon/generator.gleam", 738).
-spec option_edges(list(EYC)) -> list(gleam@option:option(EYC)).
option_edges(Inner_edges) ->
case Inner_edges of
[] ->
[none];
[First | _] ->
[none, {some, First}]
end.
-file("src/metamon/generator.gleam", 768).
-spec override_edges(generator(EYU), list(EYU)) -> generator(EYU).
override_edges(G, New) ->
{generator, erlang:element(2, G), New}.
-file("src/metamon/generator.gleam", 523).
?DOC(" A single ASCII lowercase letter generator (a-z).\n").
-spec ascii_lower() -> generator(binary()).
ascii_lower() ->
Cp_gen = int(metamon@generator@range:constant(97, 122)),
_pipe = map(Cp_gen, fun codepoint_to_string/1),
override_edges(_pipe, [<<"a"/utf8>>, <<"z"/utf8>>, <<"m"/utf8>>]).
-file("src/metamon/generator.gleam", 530).
?DOC(" A single ASCII uppercase letter generator (A-Z).\n").
-spec ascii_upper() -> generator(binary()).
ascii_upper() ->
Cp_gen = int(metamon@generator@range:constant(65, 90)),
_pipe = map(Cp_gen, fun codepoint_to_string/1),
override_edges(_pipe, [<<"A"/utf8>>, <<"Z"/utf8>>, <<"M"/utf8>>]).
-file("src/metamon/generator.gleam", 542).
?DOC(" A single ASCII digit (0-9).\n").
-spec ascii_digit() -> generator(binary()).
ascii_digit() ->
Cp_gen = int(metamon@generator@range:constant(48, 57)),
_pipe = map(Cp_gen, fun codepoint_to_string/1),
override_edges(_pipe, [<<"0"/utf8>>, <<"9"/utf8>>, <<"5"/utf8>>]).
-file("src/metamon/generator.gleam", 554).
?DOC(" A single ASCII printable character (space..~).\n").
-spec ascii_printable() -> generator(binary()).
ascii_printable() ->
Cp_gen = int(metamon@generator@range:constant(32, 126)),
_pipe = map(Cp_gen, fun codepoint_to_string/1),
override_edges(
_pipe,
[<<" "/utf8>>, <<"0"/utf8>>, <<"A"/utf8>>, <<"z"/utf8>>, <<"~"/utf8>>]
).
-file("src/metamon/generator.gleam", 782).
-spec take(list(EZE), integer()) -> list(EZE).
take(Items, N) ->
case {N, Items} of
{_, []} ->
[];
{N@1, _} when N@1 =< 0 ->
[];
{N@2, [First | Rest]} ->
[First | take(Rest, N@2 - 1)]
end.
-file("src/metamon/generator.gleam", 772).
-spec cartesian_capped(list(EYY), list(EZA), fun((EYY, EZA) -> EZC), integer()) -> list(EZC).
cartesian_capped(Left, Right, Combine, Cap) ->
_pipe = gleam@list:flat_map(
Left,
fun(L) -> gleam@list:map(Right, fun(R) -> Combine(L, R) end) end
),
take(_pipe, Cap).
-file("src/metamon/generator.gleam", 790).
-spec list_at(list(EZH), integer()) -> gleam@option:option(EZH).
list_at(Items, Index) ->
case {Items, Index} of
{[], _} ->
none;
{[First | _], 0} ->
{some, First};
{[_ | Rest], I} ->
list_at(Rest, I - 1)
end.
-file("src/metamon/generator.gleam", 798).
-spec list_append(list(EZK), list(EZK)) -> list(EZK).
list_append(Left, Right) ->
case Left of
[] ->
Right;
[First | Rest] ->
[First | list_append(Rest, Right)]
end.
-file("src/metamon/generator.gleam", 91).
?DOC(
" Add a fixed list of must-try inputs that the runner will exercise\n"
" before random generation.\n"
).
-spec with_examples(generator(EPW), list(EPW)) -> generator(EPW).
with_examples(G, Examples) ->
{generator,
erlang:element(2, G),
list_append(erlang:element(3, G), Examples)}.
-file("src/metamon/generator.gleam", 97).
?DOC(
" Append more edges to an existing generator. Symmetric to\n"
" `with_examples` but reads better for library authors.\n"
).
-spec add_edges(generator(EQA), list(EQA)) -> generator(EQA).
add_edges(G, More) ->
{generator, erlang:element(2, G), list_append(erlang:element(3, G), More)}.
-file("src/metamon/generator.gleam", 754).
-spec result_edges(list(EYN), list(EYP)) -> list({ok, EYN} | {error, EYP}).
result_edges(Ok_edges, Err_edges) ->
Ok_part = case Ok_edges of
[] ->
[];
[First | _] ->
[{ok, First}]
end,
Err_part = case Err_edges of
[] ->
[];
[First@1 | _] ->
[{error, First@1}]
end,
list_append(Ok_part, Err_part).
-file("src/metamon/generator.gleam", 128).
?DOC(
" Monadic bind. Edges are taken from the outer generator only (the\n"
" inner edges depend on the value, which is not safe to enumerate at\n"
" composition time without running the inner generator with a fixed\n"
" seed).\n"
).
-spec bind(generator(EQN), fun((EQN) -> generator(EQP))) -> generator(EQP).
bind(G, K) ->
{generator,
fun(S, Size) ->
{S_outer, S_inner} = metamon@generator@seed:split(S),
Outer_tree = (erlang:element(2, G))(S_outer, Size),
metamon@generator@tree:bind(
Outer_tree,
fun(Value) ->
Inner_gen = K(Value),
(erlang:element(2, Inner_gen))(S_inner, Size)
end
)
end,
begin
_pipe = gleam@list:flat_map(
erlang:element(3, G),
fun(A) -> erlang:element(3, K(A)) end
),
take(_pipe, 16)
end}.
-file("src/metamon/generator.gleam", 145).
?DOC(
" Applicative map over two independent generators. Shrinking holds one\n"
" component fixed while shrinking the other (via `tree.zip`).\n"
).
-spec map2(generator(EQS), generator(EQU), fun((EQS, EQU) -> EQW)) -> generator(EQW).
map2(G1, G2, F) ->
{generator,
fun(S, Size) ->
{S1, S2} = metamon@generator@seed:split(S),
Zipped = metamon@generator@tree:zip(
(erlang:element(2, G1))(S1, Size),
(erlang:element(2, G2))(S2, Size)
),
metamon@generator@tree:map(
Zipped,
fun(Pair) ->
F(erlang:element(1, Pair), erlang:element(2, Pair))
end
)
end,
cartesian_capped(erlang:element(3, G1), erlang:element(3, G2), F, 16)}.
-file("src/metamon/generator.gleam", 161).
?DOC(" Three-way applicative map.\n").
-spec map3(
generator(EQY),
generator(ERA),
generator(ERC),
fun((EQY, ERA, ERC) -> ERE)
) -> generator(ERE).
map3(G1, G2, G3, F) ->
map2(
map2(G1, G2, fun(A, B) -> {A, B} end),
G3,
fun(Pair, C) ->
F(erlang:element(1, Pair), erlang:element(2, Pair), C)
end
).
-file("src/metamon/generator.gleam", 173).
?DOC(" Four-way applicative map.\n").
-spec map4(
generator(ERG),
generator(ERI),
generator(ERK),
generator(ERM),
fun((ERG, ERI, ERK, ERM) -> ERO)
) -> generator(ERO).
map4(G1, G2, G3, G4, F) ->
map2(
map3(G1, G2, G3, fun(A, B, C) -> {A, B, C} end),
G4,
fun(Triple, D) ->
F(
erlang:element(1, Triple),
erlang:element(2, Triple),
erlang:element(3, Triple),
D
)
end
).
-file("src/metamon/generator.gleam", 186).
?DOC(" Five-way applicative map.\n").
-spec map5(
generator(ERQ),
generator(ERS),
generator(ERU),
generator(ERW),
generator(ERY),
fun((ERQ, ERS, ERU, ERW, ERY) -> ESA)
) -> generator(ESA).
map5(G1, G2, G3, G4, G5, F) ->
map2(
map4(G1, G2, G3, G4, fun(A, B, C, D) -> {A, B, C, D} end),
G5,
fun(Quad, E) ->
F(
erlang:element(1, Quad),
erlang:element(2, Quad),
erlang:element(3, Quad),
erlang:element(4, Quad),
E
)
end
).
-file("src/metamon/generator.gleam", 200).
?DOC(" Six-way applicative map.\n").
-spec map6(
generator(ESC),
generator(ESE),
generator(ESG),
generator(ESI),
generator(ESK),
generator(ESM),
fun((ESC, ESE, ESG, ESI, ESK, ESM) -> ESO)
) -> generator(ESO).
map6(G1, G2, G3, G4, G5, G6, F) ->
map2(
map5(G1, G2, G3, G4, G5, fun(A, B, C, D, E) -> {A, B, C, D, E} end),
G6,
fun(Quint, X) ->
F(
erlang:element(1, Quint),
erlang:element(2, Quint),
erlang:element(3, Quint),
erlang:element(4, Quint),
erlang:element(5, Quint),
X
)
end
).
-file("src/metamon/generator.gleam", 217).
?DOC(" Pair two independent generators.\n").
-spec tuple2(generator(ESQ), generator(ESS)) -> generator({ESQ, ESS}).
tuple2(G1, G2) ->
map2(G1, G2, fun(A, B) -> {A, B} end).
-file("src/metamon/generator.gleam", 222).
?DOC(" Triple of independent generators.\n").
-spec tuple3(generator(ESV), generator(ESX), generator(ESZ)) -> generator({ESV,
ESX,
ESZ}).
tuple3(G1, G2, G3) ->
map3(G1, G2, G3, fun(A, B, C) -> {A, B, C} end).
-file("src/metamon/generator.gleam", 231).
?DOC(" Quadruple of independent generators.\n").
-spec tuple4(generator(ETC), generator(ETE), generator(ETG), generator(ETI)) -> generator({ETC,
ETE,
ETG,
ETI}).
tuple4(G1, G2, G3, G4) ->
map4(G1, G2, G3, G4, fun(A, B, C, D) -> {A, B, C, D} end).
-file("src/metamon/generator.gleam", 241).
?DOC(" Quintuple of independent generators.\n").
-spec tuple5(
generator(ETL),
generator(ETN),
generator(ETP),
generator(ETR),
generator(ETT)
) -> generator({ETL, ETN, ETP, ETR, ETT}).
tuple5(G1, G2, G3, G4, G5) ->
map5(G1, G2, G3, G4, G5, fun(A, B, C, D, E) -> {A, B, C, D, E} end).
-file("src/metamon/generator.gleam", 255).
?DOC(
" Pick uniformly from a non-empty list of generators. Edges are the\n"
" concatenation of all branch edges.\n"
).
-spec one_of(list(generator(ETW))) -> generator(ETW).
one_of(Generators) ->
First@1 = case Generators of
[First | _] -> First;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"metamon.one_of: empty list"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"metamon/generator"/utf8>>,
function => <<"one_of"/utf8>>,
line => 256,
value => _assert_fail,
start => 7183,
'end' => 7218,
pattern_start => 7194,
pattern_end => 7205})
end,
Count = erlang:length(Generators),
{generator,
fun(S, Size) ->
{Idx, S_rest} = metamon@generator@seed:next_int_in(S, 0, Count - 1),
Chosen = case list_at(Generators, Idx) of
{some, G} ->
G;
none ->
First@1
end,
(erlang:element(2, Chosen))(S_rest, Size)
end,
begin
_pipe = gleam@list:flat_map(
Generators,
fun(G@1) -> erlang:element(3, G@1) end
),
take(_pipe, 16)
end}.
-file("src/metamon/generator.gleam", 275).
?DOC(
" Pick uniformly from a non-empty list of values. Equivalent to\n"
" `one_of(list.map(values, return))` and inherits the same edge\n"
" behaviour: every value is an edge.\n"
).
-spec element_of(list(EUA)) -> generator(EUA).
element_of(Values) ->
case Values of
[_ | _] -> nil;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"metamon.element_of: empty list"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"metamon/generator"/utf8>>,
function => <<"element_of"/utf8>>,
line => 276,
value => _assert_fail,
start => 7867,
'end' => 7894,
pattern_start => 7878,
pattern_end => 7885})
end,
one_of(gleam@list:map(Values, fun return/1)).
-file("src/metamon/generator.gleam", 283).
?DOC(
" Weighted choice over a non-empty list of `(weight, generator)` pairs.\n"
" Weights must be positive integers; non-positive weights are treated\n"
" as `1`.\n"
).
-spec frequency(list({integer(), generator(EUD)})) -> generator(EUD).
frequency(Weighted) ->
First@1 = case Weighted of
[{_, First} | _] -> First;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"metamon.frequency: empty list"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"metamon/generator"/utf8>>,
function => <<"frequency"/utf8>>,
line => 284,
value => _assert_fail,
start => 8202,
'end' => 8241,
pattern_start => 8213,
pattern_end => 8230})
end,
Normalised = gleam@list:map(
Weighted,
fun(Pair) -> case erlang:element(1, Pair) < 1 of
true ->
{1, erlang:element(2, Pair)};
false ->
Pair
end end
),
Total = gleam@list:fold(
Normalised,
0,
fun(Acc, Pair@1) -> Acc + erlang:element(1, Pair@1) end
),
{generator,
fun(S, Size) ->
{Roll, S_rest} = metamon@generator@seed:next_int_in(S, 0, Total - 1),
Chosen = pick_by_weight(Normalised, Roll, First@1),
(erlang:element(2, Chosen))(S_rest, Size)
end,
begin
_pipe = gleam@list:flat_map(
Normalised,
fun(Pair@2) -> erlang:element(3, erlang:element(2, Pair@2)) end
),
take(_pipe, 16)
end}.
-file("src/metamon/generator.gleam", 434).
?DOC(" `True` or `False`, uniformly.\n").
-spec bool() -> generator(boolean()).
bool() ->
one_of([return(true), return(false)]).
-file("src/metamon/generator.gleam", 537).
?DOC(" A single ASCII letter generator (a-zA-Z).\n").
-spec ascii_letter() -> generator(binary()).
ascii_letter() ->
one_of([ascii_lower(), ascii_upper()]).
-file("src/metamon/generator.gleam", 549).
?DOC(" A single ASCII alphanumeric character.\n").
-spec ascii_alphanumeric() -> generator(binary()).
ascii_alphanumeric() ->
one_of([ascii_letter(), ascii_digit()]).
-file("src/metamon/generator.gleam", 561).
?DOC(" A Unicode scalar value (excluding surrogates).\n").
-spec unicode_codepoint() -> generator(integer()).
unicode_codepoint() ->
_pipe = one_of(
[int(metamon@generator@range:constant(0, 16#D7FF)),
int(metamon@generator@range:constant(16#E000, 16#10FFFF))]
),
override_edges(_pipe, [0, 32, 65, 16#4E00, 16#1F600]).
-file("src/metamon/generator.gleam", 714).
?DOC(
" Generate a `dict.Dict(k, v)` of size in `len`. Duplicate keys are\n"
" resolved by last-write-wins (the standard `dict.from_list`\n"
" semantics).\n"
).
-spec dict_of(generator(EXN), generator(EXP), metamon@generator@range:range()) -> generator(gleam@dict:dict(EXN, EXP)).
dict_of(Key, Value, Len) ->
_pipe = list_of(tuple2(Key, Value), Len),
map(_pipe, fun maps:from_list/1).
-file("src/metamon/generator.gleam", 730).
?DOC(" `Some` / `None` of an inner generator.\n").
-spec option_of(generator(EXY)) -> generator(gleam@option:option(EXY)).
option_of(G) ->
_pipe = frequency(
[{1, return(none)}, {3, map(G, fun(Field@0) -> {some, Field@0} end)}]
),
override_edges(_pipe, option_edges(erlang:element(3, G))).
-file("src/metamon/generator.gleam", 746).
?DOC(" `Ok` / `Error` of two inner generators.\n").
-spec result_of(generator(EYG), generator(EYI)) -> generator({ok, EYG} |
{error, EYI}).
result_of(Ok, Err) ->
_pipe = frequency(
[{3, map(Ok, fun(Field@0) -> {ok, Field@0} end)},
{1, map(Err, fun(Field@0) -> {error, Field@0} end)}]
),
override_edges(
_pipe,
result_edges(erlang:element(3, Ok), erlang:element(3, Err))
).
-file("src/metamon/generator.gleam", 350).
?DOC(
" Reject values that fail `predicate`. The implementation retries\n"
" internally up to `filter_retry_limit` times before panicking; this\n"
" is the canonical \"filter is too strict\" failure and is preferable\n"
" to silently misreporting the property.\n"
"\n"
" Edges are filtered by `predicate` so user-supplied edges that fail\n"
" the predicate are silently dropped (they would never be tried\n"
" anyway).\n"
).
-spec filter(generator(EUV), fun((EUV) -> boolean())) -> generator(EUV).
filter(G, Predicate) ->
{generator,
fun(S, Size) -> filter_run(G, Predicate, S, Size, 100) end,
gleam@list:filter(erlang:element(3, G), Predicate)}.
-file("src/metamon/generator.gleam", 698).
?DOC(" Non-empty list. Wraps `list_of` and rejects empty lists from edges.\n").
-spec non_empty_list_of(generator(EXJ), metamon@generator@range:range()) -> generator(list(EXJ)).
non_empty_list_of(Element, Len) ->
_pipe = list_of(Element, Len),
filter(_pipe, fun(Items) -> case Items of
[] ->
false;
_ ->
true
end end).
-file("src/metamon/generator.gleam", 613).
?DOC(
" Filter a candidate string-edge list down to entries whose length lies\n"
" inside the user-supplied `Range`, then dedupe so the same value does\n"
" not appear multiple times across the merged edge set. Range bounds are\n"
" resolved at the maximum size (`size = max_size`) so the full\n"
" user-visible window is admitted regardless of how the range scales.\n"
).
-spec strings_in_length_range(list(binary()), metamon@generator@range:range()) -> list(binary()).
strings_in_length_range(Candidates, Len) ->
{Lo, Hi} = metamon@generator@range:bounds(Len, 99, 99),
_pipe = Candidates,
_pipe@1 = gleam@list:filter(
_pipe,
fun(S) ->
Length = string:length(S),
(Length >= Lo) andalso (Length =< Hi)
end
),
gleam@list:unique(_pipe@1).
-file("src/metamon/generator.gleam", 567).
?DOC(" A string built from `char_gen` characters with length in `len`.\n").
-spec string(generator(binary()), metamon@generator@range:range()) -> generator(binary()).
string(Char_gen, Len) ->
_pipe = list_of(Char_gen, Len),
_pipe@1 = map(_pipe, fun erlang:list_to_binary/1),
with_examples(
_pipe@1,
strings_in_length_range([<<""/utf8>>, <<" "/utf8>>, <<"\n"/utf8>>], Len)
).
-file("src/metamon/generator.gleam", 574).
?DOC(" An ASCII string with length in `len`.\n").
-spec string_ascii(metamon@generator@range:range()) -> generator(binary()).
string_ascii(Len) ->
_pipe = string(ascii_printable(), Len),
with_examples(
_pipe,
strings_in_length_range(metamon@generator@edges:strings_ascii(), Len)
).
-file("src/metamon/generator.gleam", 580).
?DOC(" A string of ASCII letters (a-zA-Z) with length in `len`.\n").
-spec string_alpha(metamon@generator@range:range()) -> generator(binary()).
string_alpha(Len) ->
string(ascii_letter(), Len).
-file("src/metamon/generator.gleam", 585).
?DOC(" A string of ASCII alphanumerics (a-zA-Z0-9) with length in `len`.\n").
-spec string_alphanumeric(metamon@generator@range:range()) -> generator(binary()).
string_alphanumeric(Len) ->
string(ascii_alphanumeric(), Len).
-file("src/metamon/generator.gleam", 590).
?DOC(" A string of ASCII digits (0-9) with length in `len`.\n").
-spec string_digit(metamon@generator@range:range()) -> generator(binary()).
string_digit(Len) ->
string(ascii_digit(), Len).
-file("src/metamon/generator.gleam", 597).
?DOC(
" A string of printable ASCII codepoints (`0x20`..`0x7E`) with length\n"
" in `len`. Differs from `string_ascii` by skipping the curated edge\n"
" cases that include control characters (`\\t`, `\\n`).\n"
).
-spec string_printable_ascii(metamon@generator@range:range()) -> generator(binary()).
string_printable_ascii(Len) ->
string(ascii_printable(), Len).
-file("src/metamon/generator.gleam", 602).
?DOC(" A Unicode-aware string (includes BiDi/emoji/NUL via edges).\n").
-spec string_unicode(metamon@generator@range:range()) -> generator(binary()).
string_unicode(Len) ->
Cp_gen = begin
_pipe = unicode_codepoint(),
map(_pipe, fun metamon_ffi:codepoint_to_string/1)
end,
_pipe@1 = string(Cp_gen, Len),
with_examples(
_pipe@1,
strings_in_length_range(metamon@generator@edges:strings_unicode(), Len)
).
-file("src/metamon/generator.gleam", 477).
?DOC(
" Bit array that is guaranteed to be valid UTF-8. `len` is the number\n"
" of codepoints (not bytes); the byte length will be larger when the\n"
" generated string contains multi-byte codepoints.\n"
).
-spec bit_array_utf8(metamon@generator@range:range()) -> generator(bitstring()).
bit_array_utf8(Len) ->
_pipe = string_unicode(Len),
map(_pipe, fun string_to_utf8_bit_array/1).