-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, set_of/2, ascii_lower/0, ascii_upper/0, ascii_digit/0, ascii_printable/0, with_examples/2, add_edges/2, string/2, string_ascii/1, bind/2, map2/3, map3/4, map4/5, map5/6, map6/7, tuple2/2, tuple3/3, tuple4/4, tuple5/5, one_of/1, frequency/1, bool/0, ascii_letter/0, ascii_alphanumeric/0, unicode_codepoint/0, string_unicode/1, dict_of/3, option_of/1, result_of/2, filter/2, non_empty_list_of/2]). -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(ENZ) :: {generator, fun((metamon@generator@seed:seed(), integer()) -> metamon@generator@tree:tree(ENZ)), list(ENZ)}. -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(EOA), metamon@generator@seed:seed(), integer()) -> metamon@generator@tree:tree(EOA). 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(EOD)) -> list(EOD). edges_of(G) -> erlang:element(3, G). -file("src/metamon/generator.gleam", 51). -spec sample_loop( generator(EOJ), metamon@generator@seed:seed(), integer(), integer(), list(EOJ) ) -> list(EOJ). 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(EOG), integer()) -> list(EOG). 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(EON), integer(), fun((EON) -> 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(EOZ)) -> generator(EOZ). 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(EPC) -> generator(EPC). 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(EPE), fun((EPE) -> EPG)) -> generator(EPG). 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", 296). -spec pick_by_weight( list({integer(), generator(ESZ)}), integer(), generator(ESZ) ) -> generator(ESZ). 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", 314). ?DOC(" Construct a generator whose strategy depends on the current size.\n"). -spec sized(fun((integer()) -> generator(ETE))) -> generator(ETE). 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", 325). ?DOC(" Override the size used by a generator.\n"). -spec resize(generator(ETH), integer()) -> generator(ETH). resize(G, New_size) -> {generator, fun(S, _) -> (erlang:element(2, G))(S, New_size) end, erlang:element(3, G)}. -file("src/metamon/generator.gleam", 330). ?DOC(" Transform the size given to a generator.\n"). -spec scale(generator(ETK), fun((integer()) -> integer())) -> generator(ETK). scale(G, F) -> {generator, fun(S, Size) -> (erlang:element(2, G))(S, F(Size)) end, erlang:element(3, G)}. -file("src/metamon/generator.gleam", 353). -spec filter_run( generator(ETQ), fun((ETQ) -> boolean()), metamon@generator@seed:seed(), integer(), integer() ) -> metamon@generator@tree:tree(ETQ). 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 => 366}); false -> {_, S2} = metamon@generator@seed:split(S), filter_run(G, Predicate, S2, Size, Retries_left - 1) end end. -file("src/metamon/generator.gleam", 379). ?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(ETT), fun((generator(ETT)) -> generator(ETT))) -> generator(ETT). 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", 413). -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", 418). -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", 458). -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", 462). -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", 399). ?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", 432). ?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", 437). ?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", 442). ?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", 447). ?DOC(" A single byte (`[0, 255]`).\n"). -spec byte() -> generator(integer()). byte() -> int(metamon@generator@range:constant(0, 255)). -file("src/metamon/generator.gleam", 477). ?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", 563). -spec codepoint_to_string(integer()) -> binary(). codepoint_to_string(Codepoint) -> metamon_ffi:codepoint_to_string(Codepoint). -file("src/metamon/generator.gleam", 610). -spec prepend_to_shrinks( metamon@generator@tree:shrinks(metamon@generator@tree:tree(EVI)), list(metamon@generator@tree:tree(EVI)) ) -> metamon@generator@tree:shrinks(metamon@generator@tree:tree(EVI)). prepend_to_shrinks(Existing, Prepended) -> metamon@generator@tree:append_shrinks( metamon@generator@tree:shrinks_from_list(Prepended), Existing ). -file("src/metamon/generator.gleam", 603). -spec attach_drop_shrinks(metamon@generator@tree:tree(list(EVD))) -> metamon@generator@tree:tree(list(EVD)). 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", 583). -spec generate_list_tree( generator(EUZ), metamon@generator@seed:seed(), integer(), integer() ) -> metamon@generator@tree:tree(list(EUZ)). 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", 617). -spec list_edges(list(EVP)) -> list(list(EVP)). list_edges(Element_edges) -> case Element_edges of [] -> [[]]; [First | _] -> [[], [First], Element_edges] end. -file("src/metamon/generator.gleam", 571). ?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(EUV), metamon@generator@range:range()) -> generator(list(EUV)). list_of(Element, Len) -> {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(erlang:element(3, Element))}. -file("src/metamon/generator.gleam", 453). ?DOC( " Bit array of byte-length within `len`. Each byte is generated\n" " uniformly.\n" ). -spec bit_array(metamon@generator@range:range()) -> generator(bitstring()). bit_array(Len) -> _pipe = list_of(byte(), Len), map(_pipe, fun bytes_to_bit_array/1). -file("src/metamon/generator.gleam", 651). ?DOC(" Generate a `set.Set(a)` of size in `len`.\n"). -spec set_of(generator(EWE), metamon@generator@range:range()) -> generator(gleam@set:set(EWE)). set_of(Element, Len) -> _pipe = list_of(Element, Len), map(_pipe, fun gleam@set:from_list/1). -file("src/metamon/generator.gleam", 665). -spec option_edges(list(EWM)) -> list(gleam@option:option(EWM)). option_edges(Inner_edges) -> case Inner_edges of [] -> [none]; [First | _] -> [none, {some, First}] end. -file("src/metamon/generator.gleam", 695). -spec override_edges(generator(EXE), list(EXE)) -> generator(EXE). override_edges(G, New) -> {generator, erlang:element(2, G), New}. -file("src/metamon/generator.gleam", 496). ?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", 503). ?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", 515). ?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", 527). ?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", 709). -spec take(list(EXO), integer()) -> list(EXO). 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", 699). -spec cartesian_capped(list(EXI), list(EXK), fun((EXI, EXK) -> EXM), integer()) -> list(EXM). 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", 717). -spec list_at(list(EXR), integer()) -> gleam@option:option(EXR). 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", 725). -spec list_append(list(EXU), list(EXU)) -> list(EXU). 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(EOR), list(EOR)) -> generator(EOR). 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(EOV), list(EOV)) -> generator(EOV). add_edges(G, More) -> {generator, erlang:element(2, G), list_append(erlang:element(3, G), More)}. -file("src/metamon/generator.gleam", 540). ?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, [<<""/utf8>>, <<" "/utf8>>, <<"\n"/utf8>>]). -file("src/metamon/generator.gleam", 547). ?DOC(" An ASCII string with length in `len`.\n"). -spec string_ascii(metamon@generator@range:range()) -> generator(binary()). string_ascii(Len) -> _pipe = string(ascii_printable(), Len), with_examples(_pipe, metamon@generator@edges:strings_ascii()). -file("src/metamon/generator.gleam", 681). -spec result_edges(list(EWX), list(EWZ)) -> list({ok, EWX} | {error, EWZ}). 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(EPI), fun((EPI) -> generator(EPK))) -> generator(EPK). 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(EPN), generator(EPP), fun((EPN, EPP) -> EPR)) -> generator(EPR). 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(EPT), generator(EPV), generator(EPX), fun((EPT, EPV, EPX) -> EPZ) ) -> generator(EPZ). 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(EQB), generator(EQD), generator(EQF), generator(EQH), fun((EQB, EQD, EQF, EQH) -> EQJ) ) -> generator(EQJ). 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(EQL), generator(EQN), generator(EQP), generator(EQR), generator(EQT), fun((EQL, EQN, EQP, EQR, EQT) -> EQV) ) -> generator(EQV). 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(EQX), generator(EQZ), generator(ERB), generator(ERD), generator(ERF), generator(ERH), fun((EQX, EQZ, ERB, ERD, ERF, ERH) -> ERJ) ) -> generator(ERJ). 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(ERL), generator(ERN)) -> generator({ERL, ERN}). 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(ERQ), generator(ERS), generator(ERU)) -> generator({ERQ, ERS, ERU}). 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(ERX), generator(ERZ), generator(ESB), generator(ESD)) -> generator({ERX, ERZ, ESB, ESD}). 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(ESG), generator(ESI), generator(ESK), generator(ESM), generator(ESO) ) -> generator({ESG, ESI, ESK, ESM, ESO}). 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(ESR))) -> generator(ESR). 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( " 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(ESV)})) -> generator(ESV). 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 => 276, value => _assert_fail, start => 7874, 'end' => 7913, pattern_start => 7885, pattern_end => 7902}) 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", 426). ?DOC(" `True` or `False`, uniformly.\n"). -spec bool() -> generator(boolean()). bool() -> one_of([return(true), return(false)]). -file("src/metamon/generator.gleam", 510). ?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", 522). ?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", 534). ?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", 553). ?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, metamon@generator@edges:strings_unicode()). -file("src/metamon/generator.gleam", 641). ?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(EVX), generator(EVZ), metamon@generator@range:range()) -> generator(gleam@dict:dict(EVX, EVZ)). dict_of(Key, Value, Len) -> _pipe = list_of(tuple2(Key, Value), Len), map(_pipe, fun maps:from_list/1). -file("src/metamon/generator.gleam", 657). ?DOC(" `Some` / `None` of an inner generator.\n"). -spec option_of(generator(EWI)) -> generator(gleam@option:option(EWI)). 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", 673). ?DOC(" `Ok` / `Error` of two inner generators.\n"). -spec result_of(generator(EWQ), generator(EWS)) -> generator({ok, EWQ} | {error, EWS}). 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", 342). ?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(ETN), fun((ETN) -> boolean())) -> generator(ETN). 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", 625). ?DOC(" Non-empty list. Wraps `list_of` and rejects empty lists from edges.\n"). -spec non_empty_list_of(generator(EVT), metamon@generator@range:range()) -> generator(list(EVT)). non_empty_list_of(Element, Len) -> _pipe = list_of(Element, Len), filter(_pipe, fun(Items) -> case Items of [] -> false; _ -> true end end).