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, bit_array_unaligned/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, map7/8, map8/9, tuple2/2, tuple3/3, tuple4/4, tuple5/5, tuple6/6, tuple7/7, tuple8/8, 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, float_special_edges/0, float_special/0, 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(ESU) :: {generator,
fun((metamon@generator@seed:seed(), integer()) -> metamon@generator@tree:tree(ESU)),
list(ESU)}.
-file("src/metamon/generator.gleam", 37).
?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(ESV), metamon@generator@seed:seed(), integer()) -> metamon@generator@tree:tree(ESV).
generate(G, S, Size) ->
(erlang:element(2, G))(S, Size).
-file("src/metamon/generator.gleam", 42).
?DOC(" Inspect the must-try edge values associated with a generator.\n").
-spec edges_of(generator(ESY)) -> list(ESY).
edges_of(G) ->
erlang:element(3, G).
-file("src/metamon/generator.gleam", 52).
-spec sample_loop(
generator(ETE),
metamon@generator@seed:seed(),
integer(),
integer(),
list(ETE)
) -> list(ETE).
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", 48).
?DOC(
" Pull `n` plain sample values, ignoring shrink trees. Useful at the\n"
" REPL or in test diagnostics.\n"
).
-spec sample(generator(ETB), integer()) -> list(ETB).
sample(G, N) ->
sample_loop(G, metamon@generator@seed:seed(0), N, 50, []).
-file("src/metamon/generator.gleam", 71).
?DOC(
" Bucket `n` generated values via `classify` for distribution sanity\n"
" checks.\n"
).
-spec statistics(generator(ETI), integer(), fun((ETI) -> 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", 104).
?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(ETU)) -> generator(ETU).
no_edges(G) ->
{generator, erlang:element(2, G), []}.
-file("src/metamon/generator.gleam", 111).
?DOC(" A constant generator that always returns `value` and has no shrinks.\n").
-spec return(ETX) -> generator(ETX).
return(Value) ->
{generator,
fun(_, _) -> metamon@generator@tree:singleton(Value) end,
[Value]}.
-file("src/metamon/generator.gleam", 118).
?DOC(" Functor map. Edges are mapped element-wise.\n").
-spec map(generator(ETZ), fun((ETZ) -> EUB)) -> generator(EUB).
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", 413).
-spec pick_by_weight(
list({integer(), generator(FBB)}),
integer(),
generator(FBB)
) -> generator(FBB).
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", 431).
?DOC(" Construct a generator whose strategy depends on the current size.\n").
-spec sized(fun((integer()) -> generator(FBG))) -> generator(FBG).
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", 442).
?DOC(" Override the size used by a generator.\n").
-spec resize(generator(FBJ), integer()) -> generator(FBJ).
resize(G, New_size) ->
{generator,
fun(S, _) -> (erlang:element(2, G))(S, New_size) end,
erlang:element(3, G)}.
-file("src/metamon/generator.gleam", 447).
?DOC(" Transform the size given to a generator.\n").
-spec scale(generator(FBM), fun((integer()) -> integer())) -> generator(FBM).
scale(G, F) ->
{generator,
fun(S, Size) -> (erlang:element(2, G))(S, F(Size)) end,
erlang:element(3, G)}.
-file("src/metamon/generator.gleam", 470).
-spec filter_run(
generator(FBS),
fun((FBS) -> boolean()),
metamon@generator@seed:seed(),
integer(),
integer()
) -> metamon@generator@tree:tree(FBS).
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 => 483});
false ->
{_, S2} = metamon@generator@seed:split(S),
filter_run(G, Predicate, S2, Size, Retries_left - 1)
end
end.
-file("src/metamon/generator.gleam", 496).
?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(FBV), fun((generator(FBV)) -> generator(FBV))) -> generator(FBV).
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", 530).
-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", 535).
-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", 607).
-spec bits_to_bit_array(list(integer())) -> bitstring().
bits_to_bit_array(Bits) ->
gleam@list:fold(Bits, <<>>, fun(Acc, B) -> <<Acc/bitstring, B:1>> end).
-file("src/metamon/generator.gleam", 611).
-spec string_to_utf8_bit_array(binary()) -> bitstring().
string_to_utf8_bit_array(S) ->
<<S/binary>>.
-file("src/metamon/generator.gleam", 615).
-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", 619).
-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", 516).
?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", 395).
-spec validate_weights(list({integer(), generator(any())}), integer()) -> nil.
validate_weights(Pairs, Index) ->
case Pairs of
[] ->
nil;
[{W, _} | Rest] ->
case W < 1 of
true ->
erlang:error(#{gleam_error => panic,
message => (<<<<<<<<"metamon.frequency: weight must be >= 1 (got "/utf8,
(erlang:integer_to_binary(W))/binary>>/binary,
" at position "/utf8>>/binary,
(erlang:integer_to_binary(Index))/binary>>/binary,
")"/utf8>>),
file => <<?FILEPATH/utf8>>,
module => <<"metamon/generator"/utf8>>,
function => <<"validate_weights"/utf8>>,
line => 401});
false ->
validate_weights(Rest, Index + 1)
end
end.
-file("src/metamon/generator.gleam", 549).
?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", 554).
?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", 559).
?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", 564).
?DOC(" A single byte (`[0, 255]`).\n").
-spec byte() -> generator(integer()).
byte() ->
int(metamon@generator@range:constant(0, 255)).
-file("src/metamon/generator.gleam", 642).
?DOC(
" Generates finite Float values uniformly in the closed interval\n"
" `[lo, hi]`. Shrinking moves toward the closest endpoint (no fancy\n"
" mantissa shrinking yet).\n"
"\n"
" This generator does **not** emit `NaN`, `±Infinity`, or denormal\n"
" values. Use `float_special` (or splice `float_special_edges()` via\n"
" `with_examples`) when codec correctness depends on those edges —\n"
" `f64.to_string(NaN)` formats as `\"NaN\"` but a parser may not accept\n"
" the token, `-0.0` round-trips differently than `0.0` through some\n"
" encoders, etc.\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", 708).
-spec negative_zero() -> float().
negative_zero() ->
+0.0 * -1.0.
-file("src/metamon/generator.gleam", 717).
-spec pick_special(list(float()), integer()) -> float().
pick_special(Values, Index) ->
case {Values, Index} of
{[], _} ->
+0.0;
{[First | _], 0} ->
First;
{[_ | Rest], N} ->
pick_special(Rest, N - 1)
end.
-file("src/metamon/generator.gleam", 887).
-spec codepoint_to_string(integer()) -> binary().
codepoint_to_string(Codepoint) ->
metamon_ffi:codepoint_to_string(Codepoint).
-file("src/metamon/generator.gleam", 935).
-spec prepend_to_shrinks(
metamon@generator@tree:shrinks(metamon@generator@tree:tree(FDY)),
list(metamon@generator@tree:tree(FDY))
) -> metamon@generator@tree:shrinks(metamon@generator@tree:tree(FDY)).
prepend_to_shrinks(Existing, Prepended) ->
metamon@generator@tree:append_shrinks(
metamon@generator@tree:shrinks_from_list(Prepended),
Existing
).
-file("src/metamon/generator.gleam", 928).
-spec attach_drop_shrinks(metamon@generator@tree:tree(list(FDT))) -> metamon@generator@tree:tree(list(FDT)).
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", 908).
-spec generate_list_tree(
generator(FDP),
metamon@generator@seed:seed(),
integer(),
integer()
) -> metamon@generator@tree:tree(list(FDP)).
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", 942).
-spec list_edges_in_range(list(FEF), integer(), integer()) -> list(list(FEF)).
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", 895).
?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(FDL), metamon@generator@range:range()) -> generator(list(FDL)).
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", 571).
?DOC(
" Bit array whose total length in **bytes** lies inside `byte_len`.\n"
" Each byte is generated uniformly. Output is always byte-aligned\n"
" (no sub-byte tail).\n"
).
-spec bit_array(metamon@generator@range:range()) -> generator(bitstring()).
bit_array(Byte_len) ->
_pipe = list_of(byte(), Byte_len),
map(_pipe, fun bytes_to_bit_array/1).
-file("src/metamon/generator.gleam", 581).
?DOC(
" Bit array whose every byte is a printable ASCII codepoint\n"
" (`0x20`..`0x7E`). `byte_len` is, like `bit_array`, the number of\n"
" **bytes** in the resulting array. Useful for fuzzing parsers that\n"
" take `BitArray` but expect printable input (HTTP headers, MIME\n"
" types, etc.).\n"
).
-spec bit_array_printable(metamon@generator@range:range()) -> generator(bitstring()).
bit_array_printable(Byte_len) ->
_pipe = list_of(int(metamon@generator@range:constant(32, 126)), Byte_len),
map(_pipe, fun bytes_to_bit_array/1).
-file("src/metamon/generator.gleam", 602).
?DOC(
" Bit array of arbitrary bit length, possibly NOT byte-aligned.\n"
" `bit_len` is the total number of bits in the result.\n"
"\n"
" Use this generator to fuzz code paths that take a `BitArray` whose\n"
" total length is not necessarily a multiple of 8: codec internals,\n"
" length-prefixed framing, bignum bit walks, and any parser that\n"
" must reject (or handle) sub-byte tails. Reach for `bit_array` when\n"
" byte-aligned input is sufficient — it is faster and the size\n"
" metric matches byte-oriented properties more naturally.\n"
).
-spec bit_array_unaligned(metamon@generator@range:range()) -> generator(bitstring()).
bit_array_unaligned(Bit_len) ->
_pipe = list_of(int(metamon@generator@range:constant(0, 1)), Bit_len),
map(_pipe, fun bits_to_bit_array/1).
-file("src/metamon/generator.gleam", 984).
?DOC(" Generate a `set.Set(a)` of size in `len`.\n").
-spec set_of(generator(FEU), metamon@generator@range:range()) -> generator(gleam@set:set(FEU)).
set_of(Element, Len) ->
_pipe = list_of(Element, Len),
map(_pipe, fun gleam@set:from_list/1).
-file("src/metamon/generator.gleam", 998).
-spec option_edges(list(FFC)) -> list(gleam@option:option(FFC)).
option_edges(Inner_edges) ->
case Inner_edges of
[] ->
[none];
[First | _] ->
[none, {some, First}]
end.
-file("src/metamon/generator.gleam", 1028).
-spec override_edges(generator(FFU), list(FFU)) -> generator(FFU).
override_edges(G, New) ->
{generator, erlang:element(2, G), New}.
-file("src/metamon/generator.gleam", 728).
?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", 735).
?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", 747).
?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", 759).
?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", 810).
?DOC(
" A single ASCII codepoint generator (`0x00`..`0x7F`). Includes\n"
" control characters; for printable-only output use\n"
" `ascii_printable`. Curated edges cover the most useful\n"
" boundaries: NUL, `\\t`, `\\n`, space, digit `0`, letters `A` / `z`,\n"
" `~`, and DEL.\n"
).
-spec ascii_full() -> generator(binary()).
ascii_full() ->
Cp_gen = int(metamon@generator@range:constant(0, 127)),
_pipe = map(Cp_gen, fun codepoint_to_string/1),
override_edges(
_pipe,
[<<"\x{0000}"/utf8>>,
<<"\t"/utf8>>,
<<"\n"/utf8>>,
<<" "/utf8>>,
<<"0"/utf8>>,
<<"A"/utf8>>,
<<"z"/utf8>>,
<<"~"/utf8>>,
<<"\x{007F}"/utf8>>]
).
-file("src/metamon/generator.gleam", 1042).
-spec take(list(FGE), integer()) -> list(FGE).
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", 1032).
-spec cartesian_capped(list(FFY), list(FGA), fun((FFY, FGA) -> FGC), integer()) -> list(FGC).
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", 1050).
-spec list_at(list(FGH), integer()) -> gleam@option:option(FGH).
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", 1058).
-spec list_append(list(FGK), list(FGK)) -> list(FGK).
list_append(Left, Right) ->
case Left of
[] ->
Right;
[First | Rest] ->
[First | list_append(Rest, Right)]
end.
-file("src/metamon/generator.gleam", 92).
?DOC(
" Add a fixed list of must-try inputs that the runner will exercise\n"
" before random generation.\n"
).
-spec with_examples(generator(ETM), list(ETM)) -> generator(ETM).
with_examples(G, Examples) ->
{generator,
erlang:element(2, G),
list_append(erlang:element(3, G), Examples)}.
-file("src/metamon/generator.gleam", 98).
?DOC(
" Append more edges to an existing generator. Symmetric to\n"
" `with_examples` but reads better for library authors.\n"
).
-spec add_edges(generator(ETQ), list(ETQ)) -> generator(ETQ).
add_edges(G, More) ->
{generator, erlang:element(2, G), list_append(erlang:element(3, G), More)}.
-file("src/metamon/generator.gleam", 1014).
-spec result_edges(list(FFN), list(FFP)) -> list({ok, FFN} | {error, FFP}).
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", 129).
?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(EUD), fun((EUD) -> generator(EUF))) -> generator(EUF).
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", 146).
?DOC(
" Applicative map over two independent generators. Shrinking holds one\n"
" component fixed while shrinking the other (via `tree.zip`).\n"
).
-spec map2(generator(EUI), generator(EUK), fun((EUI, EUK) -> EUM)) -> generator(EUM).
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", 162).
?DOC(" Three-way applicative map.\n").
-spec map3(
generator(EUO),
generator(EUQ),
generator(EUS),
fun((EUO, EUQ, EUS) -> EUU)
) -> generator(EUU).
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", 174).
?DOC(" Four-way applicative map.\n").
-spec map4(
generator(EUW),
generator(EUY),
generator(EVA),
generator(EVC),
fun((EUW, EUY, EVA, EVC) -> EVE)
) -> generator(EVE).
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", 187).
?DOC(" Five-way applicative map.\n").
-spec map5(
generator(EVG),
generator(EVI),
generator(EVK),
generator(EVM),
generator(EVO),
fun((EVG, EVI, EVK, EVM, EVO) -> EVQ)
) -> generator(EVQ).
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", 201).
?DOC(" Six-way applicative map.\n").
-spec map6(
generator(EVS),
generator(EVU),
generator(EVW),
generator(EVY),
generator(EWA),
generator(EWC),
fun((EVS, EVU, EVW, EVY, EWA, EWC) -> EWE)
) -> generator(EWE).
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", 222).
?DOC(
" Seven-way applicative map. Like `map6` but accepts a seventh\n"
" generator. Use this for record types with seven fields when you\n"
" want to keep integrated shrinking on every component (the `bind`-\n"
" based workaround in the README's Limitations section shrinks\n"
" shallowly).\n"
).
-spec map7(
generator(EWG),
generator(EWI),
generator(EWK),
generator(EWM),
generator(EWO),
generator(EWQ),
generator(EWS),
fun((EWG, EWI, EWK, EWM, EWO, EWQ, EWS) -> EWU)
) -> generator(EWU).
map7(G1, G2, G3, G4, G5, G6, G7, F) ->
map2(
map6(
G1,
G2,
G3,
G4,
G5,
G6,
fun(A, B, C, D, E, G) -> {A, B, C, D, E, G} end
),
G7,
fun(Sext, X) ->
F(
erlang:element(1, Sext),
erlang:element(2, Sext),
erlang:element(3, Sext),
erlang:element(4, Sext),
erlang:element(5, Sext),
erlang:element(6, Sext),
X
)
end
).
-file("src/metamon/generator.gleam", 244).
?DOC(
" Eight-way applicative map. Like `map7` but accepts an eighth\n"
" generator. Stops at eight because nine is comfortably above every\n"
" record arity in the audited workload; reach for nested `map2` /\n"
" `bind` (and accept the shallow-shrinking caveat) if you genuinely\n"
" need more.\n"
).
-spec map8(
generator(EWW),
generator(EWY),
generator(EXA),
generator(EXC),
generator(EXE),
generator(EXG),
generator(EXI),
generator(EXK),
fun((EWW, EWY, EXA, EXC, EXE, EXG, EXI, EXK) -> EXM)
) -> generator(EXM).
map8(G1, G2, G3, G4, G5, G6, G7, G8, F) ->
map2(
map7(
G1,
G2,
G3,
G4,
G5,
G6,
G7,
fun(A, B, C, D, E, G, H) -> {A, B, C, D, E, G, H} end
),
G8,
fun(Sept, X) ->
F(
erlang:element(1, Sept),
erlang:element(2, Sept),
erlang:element(3, Sept),
erlang:element(4, Sept),
erlang:element(5, Sept),
erlang:element(6, Sept),
erlang:element(7, Sept),
X
)
end
).
-file("src/metamon/generator.gleam", 265).
?DOC(" Pair two independent generators.\n").
-spec tuple2(generator(EXO), generator(EXQ)) -> generator({EXO, EXQ}).
tuple2(G1, G2) ->
map2(G1, G2, fun(A, B) -> {A, B} end).
-file("src/metamon/generator.gleam", 270).
?DOC(" Triple of independent generators.\n").
-spec tuple3(generator(EXT), generator(EXV), generator(EXX)) -> generator({EXT,
EXV,
EXX}).
tuple3(G1, G2, G3) ->
map3(G1, G2, G3, fun(A, B, C) -> {A, B, C} end).
-file("src/metamon/generator.gleam", 279).
?DOC(" Quadruple of independent generators.\n").
-spec tuple4(generator(EYA), generator(EYC), generator(EYE), generator(EYG)) -> generator({EYA,
EYC,
EYE,
EYG}).
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", 289).
?DOC(" Quintuple of independent generators.\n").
-spec tuple5(
generator(EYJ),
generator(EYL),
generator(EYN),
generator(EYP),
generator(EYR)
) -> generator({EYJ, EYL, EYN, EYP, EYR}).
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", 300).
?DOC(" Sextuple of independent generators.\n").
-spec tuple6(
generator(EYU),
generator(EYW),
generator(EYY),
generator(EZA),
generator(EZC),
generator(EZE)
) -> generator({EYU, EYW, EYY, EZA, EZC, EZE}).
tuple6(G1, G2, G3, G4, G5, G6) ->
map6(
G1,
G2,
G3,
G4,
G5,
G6,
fun(A, B, C, D, E, G) -> {A, B, C, D, E, G} end
).
-file("src/metamon/generator.gleam", 312).
?DOC(" Septuple of independent generators.\n").
-spec tuple7(
generator(EZH),
generator(EZJ),
generator(EZL),
generator(EZN),
generator(EZP),
generator(EZR),
generator(EZT)
) -> generator({EZH, EZJ, EZL, EZN, EZP, EZR, EZT}).
tuple7(G1, G2, G3, G4, G5, G6, G7) ->
map7(
G1,
G2,
G3,
G4,
G5,
G6,
G7,
fun(A, B, C, D, E, G, H) -> {A, B, C, D, E, G, H} end
).
-file("src/metamon/generator.gleam", 327).
?DOC(" Octuple of independent generators.\n").
-spec tuple8(
generator(EZW),
generator(EZY),
generator(FAA),
generator(FAC),
generator(FAE),
generator(FAG),
generator(FAI),
generator(FAK)
) -> generator({EZW, EZY, FAA, FAC, FAE, FAG, FAI, FAK}).
tuple8(G1, G2, G3, G4, G5, G6, G7, G8) ->
map8(
G1,
G2,
G3,
G4,
G5,
G6,
G7,
G8,
fun(A, B, C, D, E, G, H, I) -> {A, B, C, D, E, G, H, I} end
).
-file("src/metamon/generator.gleam", 346).
?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(FAN))) -> generator(FAN).
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 => 347,
value => _assert_fail,
start => 9603,
'end' => 9638,
pattern_start => 9614,
pattern_end => 9625})
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", 366).
?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(FAR)) -> generator(FAR).
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 => 367,
value => _assert_fail,
start => 10287,
'end' => 10314,
pattern_start => 10298,
pattern_end => 10305})
end,
one_of(gleam@list:map(Values, fun return/1)).
-file("src/metamon/generator.gleam", 380).
?DOC(
" Weighted choice over a non-empty list of `(weight, generator)` pairs.\n"
"\n"
" Weights must be `>= 1`. A weight of `0` or a negative weight is a\n"
" programming error (a \"disabled\" branch silently re-enabled by the\n"
" previous coercion to `1`, or a `weight = max(0, computed)`\n"
" defensive pattern losing its safety net) and panics with a\n"
" structured message naming the offending position. Pass at least\n"
" `1` for any branch you want to keep, or remove the entry entirely\n"
" to opt it out.\n"
).
-spec frequency(list({integer(), generator(FAU)})) -> generator(FAU).
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 => 381,
value => _assert_fail,
start => 10965,
'end' => 11004,
pattern_start => 10976,
pattern_end => 10993})
end,
validate_weights(Weighted, 0),
Total = gleam@list:fold(
Weighted,
0,
fun(Acc, Pair) -> Acc + erlang:element(1, Pair) end
),
{generator,
fun(S, Size) ->
{Roll, S_rest} = metamon@generator@seed:next_int_in(S, 0, Total - 1),
Chosen = pick_by_weight(Weighted, Roll, First@1),
(erlang:element(2, Chosen))(S_rest, Size)
end,
begin
_pipe = gleam@list:flat_map(
Weighted,
fun(Pair@1) -> erlang:element(3, erlang:element(2, Pair@1)) end
),
take(_pipe, 16)
end}.
-file("src/metamon/generator.gleam", 543).
?DOC(" `True` or `False`, uniformly.\n").
-spec bool() -> generator(boolean()).
bool() ->
one_of([return(true), return(false)]).
-file("src/metamon/generator.gleam", 742).
?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", 754).
?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", 778).
?DOC(
" A Unicode scalar value (excluding surrogates).\n"
"\n"
" Draws from `[0, 0xD7FF] ∪ [0xE000, 0x10FFFF]`. The surrogate range\n"
" `[0xD800, 0xDFFF]` is intentionally excluded because Gleam strings\n"
" are UTF-8: lone surrogates would not survive\n"
" `string.from_utf_codepoints`.\n"
"\n"
" To fuzz codecs / parsers that must handle malformed UTF-8 byte\n"
" sequences (lone surrogates, overlong encodings, truncated\n"
" continuation bytes, …), drop down to `bit_array(byte_len)` and\n"
" exercise the parser at the byte level instead — those inputs\n"
" cannot exist inside a Gleam `String` and so cannot reach a\n"
" property whose generator is `string_unicode`.\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", 974).
?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(FEN), generator(FEP), metamon@generator@range:range()) -> generator(gleam@dict:dict(FEN, FEP)).
dict_of(Key, Value, Len) ->
_pipe = list_of(tuple2(Key, Value), Len),
map(_pipe, fun maps:from_list/1).
-file("src/metamon/generator.gleam", 990).
?DOC(" `Some` / `None` of an inner generator.\n").
-spec option_of(generator(FEY)) -> generator(gleam@option:option(FEY)).
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", 1006).
?DOC(" `Ok` / `Error` of two inner generators.\n").
-spec result_of(generator(FFG), generator(FFI)) -> generator({ok, FFG} |
{error, FFI}).
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", 459).
?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(FBP), fun((FBP) -> boolean())) -> generator(FBP).
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", 958).
?DOC(" Non-empty list. Wraps `list_of` and rejects empty lists from edges.\n").
-spec non_empty_list_of(generator(FEJ), metamon@generator@range:range()) -> generator(list(FEJ)).
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", 695).
?DOC(
" The list of values emitted by `float_special`. Exposed so callers\n"
" can splice them into a custom range generator via\n"
" `with_examples(my_float_gen, generator.float_special_edges())`.\n"
).
-spec float_special_edges() -> list(float()).
float_special_edges() ->
[metamon_ffi:ieee_nan(),
metamon_ffi:ieee_positive_infinity(),
metamon_ffi:ieee_negative_infinity(),
+0.0,
negative_zero(),
1.0,
metamon_ffi:ieee_smallest_positive_denormal(),
metamon_ffi:ieee_largest_finite()].
-file("src/metamon/generator.gleam", 680).
?DOC(
" IEEE 754 special-value generator: emits values that the regular\n"
" `float` generator never produces — `NaN`, `+Infinity`, `-Infinity`,\n"
" the smallest positive denormal, the largest finite double, plus the\n"
" \"ordinary\" anchors `0.0`, `-0.0`, `1.0`. Every value is also an\n"
" edge, so the runner tries each one before falling back to random.\n"
"\n"
" Use this when codec / serialisation correctness depends on IEEE\n"
" edges (`f64.to_string(NaN)` formats as `\"NaN\"`, but a parser may not\n"
" accept that token; `-0.0` round-trips differently than `0.0` through\n"
" some encoders, etc.). The values pass through metamon's edge pipeline\n"
" unchanged because the runner does not perform arithmetic on edge\n"
" values — but downstream user code that does arithmetic on the\n"
" generated value will raise `badarith` on the BEAM, exactly as\n"
" IEEE 754 expects.\n"
"\n"
" Target asymmetry: on JavaScript, the non-finite slots return\n"
" genuine `NaN` / `±Infinity`. On the BEAM, they return finite\n"
" sentinels (largest finite double for `NaN` / `+Infinity`, the\n"
" negation thereof for `-Infinity`) because the BEAM has no portable\n"
" way to construct a non-finite double from pure Erlang. Properties\n"
" that strictly require genuine non-finite values must run on the\n"
" JavaScript target.\n"
).
-spec float_special() -> generator(float()).
float_special() ->
Edges = float_special_edges(),
{generator,
fun(S, _) ->
{Idx, _} = metamon@generator@seed:next_int_in(S, 0, 7),
Value = pick_special(Edges, Idx),
metamon@generator@tree:singleton(Value)
end,
Edges}.
-file("src/metamon/generator.gleam", 873).
?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", 784).
?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", 800).
?DOC(
" An ASCII string with length in `len`.\n"
"\n"
" Random sampling spans the full ASCII range (`0x00`..`0x7F`),\n"
" including control characters (`0x00`..`0x1F`, `0x7F`). Reach for\n"
" `string_printable_ascii` if your property cannot tolerate control\n"
" bytes; this function is for fuzzing parsers / serialisers that\n"
" must handle the whole 7-bit space.\n"
"\n"
" Curated edges include both control bytes (`\\t`, `\\n`) and\n"
" printable boundaries (`\" \"`, `\"~\"`).\n"
).
-spec string_ascii(metamon@generator@range:range()) -> generator(binary()).
string_ascii(Len) ->
_pipe = string(ascii_full(), Len),
with_examples(
_pipe,
strings_in_length_range(metamon@generator@edges:strings_ascii(), Len)
).
-file("src/metamon/generator.gleam", 827).
?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", 832).
?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", 837).
?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", 844).
?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", 862).
?DOC(
" A Unicode-aware string (includes BiDi/emoji/NUL via edges).\n"
"\n"
" Produces **valid UTF-8 scalar values only** — see\n"
" `unicode_codepoint` for the codepoint set. Lone surrogates and\n"
" other malformed UTF-8 byte sequences are not reachable through\n"
" this generator because they cannot exist inside a Gleam `String`.\n"
" To fuzz parsers that must accept or reject malformed UTF-8 input,\n"
" use `bit_array(byte_len)` and operate at the byte level.\n"
"\n"
" No bias toward Unicode normalisation boundaries (NFC vs NFD) is\n"
" built in: equivalent forms like `\"é\" (U+00E9)` and\n"
" `\"e\\u{0301}\" (U+0065 U+0301)` are sampled independently. Pre-compose\n"
" any equivalence-class edges via `with_examples` if your property\n"
" depends on them.\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", 589).
?DOC(
" Bit array that is guaranteed to be valid UTF-8. `codepoint_len` is\n"
" the number of Unicode codepoints; the resulting byte length will\n"
" be larger when the random string contains multi-byte codepoints.\n"
).
-spec bit_array_utf8(metamon@generator@range:range()) -> generator(bitstring()).
bit_array_utf8(Codepoint_len) ->
_pipe = string_unicode(Codepoint_len),
map(_pipe, fun string_to_utf8_bit_array/1).