-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(ESV) :: {generator, fun((metamon@generator@seed:seed(), integer()) -> metamon@generator@tree:tree(ESV)), list(ESV)}. -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(ESW), metamon@generator@seed:seed(), integer()) -> metamon@generator@tree:tree(ESW). 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(ESZ)) -> list(ESZ). edges_of(G) -> erlang:element(3, G). -file("src/metamon/generator.gleam", 52). -spec sample_loop( generator(ETF), metamon@generator@seed:seed(), integer(), integer(), list(ETF) ) -> list(ETF). 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(ETC), integer()) -> list(ETC). 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(ETJ), integer(), fun((ETJ) -> 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(ETV)) -> generator(ETV). 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(ETY) -> generator(ETY). 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(EUA), fun((EUA) -> EUC)) -> generator(EUC). 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(FBC)}), integer(), generator(FBC) ) -> generator(FBC). 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(FBH))) -> generator(FBH). 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(FBK), integer()) -> generator(FBK). 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(FBN), fun((integer()) -> integer())) -> generator(FBN). 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(FBT), fun((FBT) -> boolean()), metamon@generator@seed:seed(), integer(), integer() ) -> metamon@generator@tree:tree(FBT). 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 => 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(FBW), fun((generator(FBW)) -> generator(FBW))) -> generator(FBW). 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) -> <> end). -file("src/metamon/generator.gleam", 611). -spec string_to_utf8_bit_array(binary()) -> bitstring(). string_to_utf8_bit_array(S) -> <>. -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) -> <> 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 => <>, 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(FDZ)), list(metamon@generator@tree:tree(FDZ)) ) -> metamon@generator@tree:shrinks(metamon@generator@tree:tree(FDZ)). 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(FDU))) -> metamon@generator@tree:tree(list(FDU)). 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(FDQ), metamon@generator@seed:seed(), integer(), integer() ) -> metamon@generator@tree:tree(list(FDQ)). 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(FEG), integer(), integer()) -> list(list(FEG)). 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(FDM), metamon@generator@range:range()) -> generator(list(FDM)). 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(FEV), metamon@generator@range:range()) -> generator(gleam@set:set(FEV)). 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(FFD)) -> list(gleam@option:option(FFD)). option_edges(Inner_edges) -> case Inner_edges of [] -> [none]; [First | _] -> [none, {some, First}] end. -file("src/metamon/generator.gleam", 1028). -spec override_edges(generator(FFV), list(FFV)) -> generator(FFV). 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(FGF), integer()) -> list(FGF). 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(FFZ), list(FGB), fun((FFZ, FGB) -> FGD), integer()) -> list(FGD). 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(FGI), integer()) -> gleam@option:option(FGI). 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(FGL), list(FGL)) -> list(FGL). 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(ETN), list(ETN)) -> generator(ETN). 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(ETR), list(ETR)) -> generator(ETR). 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(FFO), list(FFQ)) -> list({ok, FFO} | {error, FFQ}). 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(EUE), fun((EUE) -> generator(EUG))) -> generator(EUG). 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(EUJ), generator(EUL), fun((EUJ, EUL) -> EUN)) -> generator(EUN). 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(EUP), generator(EUR), generator(EUT), fun((EUP, EUR, EUT) -> EUV) ) -> generator(EUV). 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(EUX), generator(EUZ), generator(EVB), generator(EVD), fun((EUX, EUZ, EVB, EVD) -> EVF) ) -> generator(EVF). 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(EVH), generator(EVJ), generator(EVL), generator(EVN), generator(EVP), fun((EVH, EVJ, EVL, EVN, EVP) -> EVR) ) -> generator(EVR). 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(EVT), generator(EVV), generator(EVX), generator(EVZ), generator(EWB), generator(EWD), fun((EVT, EVV, EVX, EVZ, EWB, EWD) -> EWF) ) -> generator(EWF). 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(EWH), generator(EWJ), generator(EWL), generator(EWN), generator(EWP), generator(EWR), generator(EWT), fun((EWH, EWJ, EWL, EWN, EWP, EWR, EWT) -> EWV) ) -> generator(EWV). 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(EWX), generator(EWZ), generator(EXB), generator(EXD), generator(EXF), generator(EXH), generator(EXJ), generator(EXL), fun((EWX, EWZ, EXB, EXD, EXF, EXH, EXJ, EXL) -> EXN) ) -> generator(EXN). 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(EXP), generator(EXR)) -> generator({EXP, EXR}). 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(EXU), generator(EXW), generator(EXY)) -> generator({EXU, EXW, EXY}). 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(EYB), generator(EYD), generator(EYF), generator(EYH)) -> generator({EYB, EYD, EYF, EYH}). 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(EYK), generator(EYM), generator(EYO), generator(EYQ), generator(EYS) ) -> generator({EYK, EYM, EYO, EYQ, EYS}). 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(EYV), generator(EYX), generator(EYZ), generator(EZB), generator(EZD), generator(EZF) ) -> generator({EYV, EYX, EYZ, EZB, EZD, EZF}). 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(EZI), generator(EZK), generator(EZM), generator(EZO), generator(EZQ), generator(EZS), generator(EZU) ) -> generator({EZI, EZK, EZM, EZO, EZQ, EZS, EZU}). 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(EZX), generator(EZZ), generator(FAB), generator(FAD), generator(FAF), generator(FAH), generator(FAJ), generator(FAL) ) -> generator({EZX, EZZ, FAB, FAD, FAF, FAH, FAJ, FAL}). 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(FAO))) -> generator(FAO). 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 => 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(FAS)) -> generator(FAS). 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 => 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(FAV)})) -> generator(FAV). 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 => 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(FEO), generator(FEQ), metamon@generator@range:range()) -> generator(gleam@dict:dict(FEO, FEQ)). 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(FEZ)) -> generator(gleam@option:option(FEZ)). 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(FFH), generator(FFJ)) -> generator({ok, FFH} | {error, FFJ}). 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(FBQ), fun((FBQ) -> boolean())) -> generator(FBQ). 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(FEK), metamon@generator@range:range()) -> generator(list(FEK)). 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).