-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, 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(EPG) :: {generator, fun((metamon@generator@seed:seed(), integer()) -> metamon@generator@tree:tree(EPG)), list(EPG)}. -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(EPH), metamon@generator@seed:seed(), integer()) -> metamon@generator@tree:tree(EPH). 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(EPK)) -> list(EPK). edges_of(G) -> erlang:element(3, G). -file("src/metamon/generator.gleam", 51). -spec sample_loop( generator(EPQ), metamon@generator@seed:seed(), integer(), integer(), list(EPQ) ) -> list(EPQ). 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(EPN), integer()) -> list(EPN). 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(EPU), integer(), fun((EPU) -> 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(EQG)) -> generator(EQG). 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(EQJ) -> generator(EQJ). 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(EQL), fun((EQL) -> EQN)) -> generator(EQN). 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(EUJ)}), integer(), generator(EUJ) ) -> generator(EUJ). 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(EUO))) -> generator(EUO). 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(EUR), integer()) -> generator(EUR). 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(EUU), fun((integer()) -> integer())) -> generator(EUU). 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(EVA), fun((EVA) -> boolean()), metamon@generator@seed:seed(), integer(), integer() ) -> metamon@generator@tree:tree(EVA). 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 => <>, 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(EVD), fun((generator(EVD)) -> generator(EVD))) -> generator(EVD). 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", 498). -spec bits_to_bit_array(list(integer())) -> bitstring(). bits_to_bit_array(Bits) -> gleam@list:fold(Bits, <<>>, fun(Acc, B) -> <> end). -file("src/metamon/generator.gleam", 502). -spec string_to_utf8_bit_array(binary()) -> bitstring(). string_to_utf8_bit_array(S) -> <>. -file("src/metamon/generator.gleam", 506). -spec bytes_to_bit_array(list(integer())) -> bitstring(). bytes_to_bit_array(Bytes) -> gleam@list:fold(Bytes, <<>>, fun(Acc, B) -> <> end). -file("src/metamon/generator.gleam", 510). -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", 525). ?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", 678). -spec codepoint_to_string(integer()) -> binary(). codepoint_to_string(Codepoint) -> metamon_ffi:codepoint_to_string(Codepoint). -file("src/metamon/generator.gleam", 726). -spec prepend_to_shrinks( metamon@generator@tree:shrinks(metamon@generator@tree:tree(EXD)), list(metamon@generator@tree:tree(EXD)) ) -> metamon@generator@tree:shrinks(metamon@generator@tree:tree(EXD)). prepend_to_shrinks(Existing, Prepended) -> metamon@generator@tree:append_shrinks( metamon@generator@tree:shrinks_from_list(Prepended), Existing ). -file("src/metamon/generator.gleam", 719). -spec attach_drop_shrinks(metamon@generator@tree:tree(list(EWY))) -> metamon@generator@tree:tree(list(EWY)). 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", 699). -spec generate_list_tree( generator(EWU), metamon@generator@seed:seed(), integer(), integer() ) -> metamon@generator@tree:tree(list(EWU)). 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", 733). -spec list_edges_in_range(list(EXK), integer(), integer()) -> list(list(EXK)). 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", 686). ?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(EWQ), metamon@generator@range:range()) -> generator(list(EWQ)). 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", 462). ?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", 472). ?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", 493). ?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", 775). ?DOC(" Generate a `set.Set(a)` of size in `len`.\n"). -spec set_of(generator(EXZ), metamon@generator@range:range()) -> generator(gleam@set:set(EXZ)). set_of(Element, Len) -> _pipe = list_of(Element, Len), map(_pipe, fun gleam@set:from_list/1). -file("src/metamon/generator.gleam", 789). -spec option_edges(list(EYH)) -> list(gleam@option:option(EYH)). option_edges(Inner_edges) -> case Inner_edges of [] -> [none]; [First | _] -> [none, {some, First}] end. -file("src/metamon/generator.gleam", 819). -spec override_edges(generator(EYZ), list(EYZ)) -> generator(EYZ). override_edges(G, New) -> {generator, erlang:element(2, G), New}. -file("src/metamon/generator.gleam", 544). ?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", 551). ?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", 563). ?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", 575). ?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", 614). ?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", 833). -spec take(list(EZJ), integer()) -> list(EZJ). 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", 823). -spec cartesian_capped(list(EZD), list(EZF), fun((EZD, EZF) -> EZH), integer()) -> list(EZH). 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", 841). -spec list_at(list(EZM), integer()) -> gleam@option:option(EZM). 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", 849). -spec list_append(list(EZP), list(EZP)) -> list(EZP). 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(EPY), list(EPY)) -> generator(EPY). 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(EQC), list(EQC)) -> generator(EQC). add_edges(G, More) -> {generator, erlang:element(2, G), list_append(erlang:element(3, G), More)}. -file("src/metamon/generator.gleam", 805). -spec result_edges(list(EYS), list(EYU)) -> list({ok, EYS} | {error, EYU}). 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(EQP), fun((EQP) -> generator(EQR))) -> generator(EQR). 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(EQU), generator(EQW), fun((EQU, EQW) -> EQY)) -> generator(EQY). 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(ERA), generator(ERC), generator(ERE), fun((ERA, ERC, ERE) -> ERG) ) -> generator(ERG). 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(ERI), generator(ERK), generator(ERM), generator(ERO), fun((ERI, ERK, ERM, ERO) -> ERQ) ) -> generator(ERQ). 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(ERS), generator(ERU), generator(ERW), generator(ERY), generator(ESA), fun((ERS, ERU, ERW, ERY, ESA) -> ESC) ) -> generator(ESC). 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(ESE), generator(ESG), generator(ESI), generator(ESK), generator(ESM), generator(ESO), fun((ESE, ESG, ESI, ESK, ESM, ESO) -> ESQ) ) -> generator(ESQ). 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(ESS), generator(ESU)) -> generator({ESS, ESU}). 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(ESX), generator(ESZ), generator(ETB)) -> generator({ESX, ESZ, ETB}). 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(ETE), generator(ETG), generator(ETI), generator(ETK)) -> generator({ETE, ETG, ETI, ETK}). 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(ETN), generator(ETP), generator(ETR), generator(ETT), generator(ETV) ) -> generator({ETN, ETP, ETR, ETT, ETV}). 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(ETY))) -> generator(ETY). 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 => <>, 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(EUC)) -> generator(EUC). element_of(Values) -> case Values of [_ | _] -> nil; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"metamon.element_of: empty list"/utf8>>, file => <>, 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(EUF)})) -> generator(EUF). frequency(Weighted) -> First@1 = case Weighted of [{_, First} | _] -> First; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"metamon.frequency: empty list"/utf8>>, file => <>, 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", 558). ?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", 570). ?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", 582). ?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", 765). ?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(EXS), generator(EXU), metamon@generator@range:range()) -> generator(gleam@dict:dict(EXS, EXU)). dict_of(Key, Value, Len) -> _pipe = list_of(tuple2(Key, Value), Len), map(_pipe, fun maps:from_list/1). -file("src/metamon/generator.gleam", 781). ?DOC(" `Some` / `None` of an inner generator.\n"). -spec option_of(generator(EYD)) -> generator(gleam@option:option(EYD)). 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", 797). ?DOC(" `Ok` / `Error` of two inner generators.\n"). -spec result_of(generator(EYL), generator(EYN)) -> generator({ok, EYL} | {error, EYN}). 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(EUX), fun((EUX) -> boolean())) -> generator(EUX). 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", 749). ?DOC(" Non-empty list. Wraps `list_of` and rejects empty lists from edges.\n"). -spec non_empty_list_of(generator(EXO), metamon@generator@range:range()) -> generator(list(EXO)). 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", 664). ?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", 588). ?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", 604). ?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", 631). ?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", 636). ?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", 641). ?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", 648). ?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", 653). ?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", 480). ?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).