-module(metamon@generator@tree). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/metamon/generator/tree.gleam"). -export([no_shrinks/0, shrinks_from_list/1, map_shrinks/2, append_shrinks/2, shrinks_take/2, shrinks_to_list/1, shrinks_find/2, singleton/1, unfold/2, from_list/2, map/2, bind/2, zip/2, outline/3, filter_shrinks/2, filter/2]). -export_type([shrinks/1, shrink_step/1, tree/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( " A lazy rose tree: a value paired with an on-demand stream of \"smaller\"\n" " alternatives. Generators in metamon return `Tree(a)` instead of bare\n" " values so shrinking is always available without separate plumbing.\n" "\n" " The lazy stream type `Shrinks(a)` is defined locally instead of using\n" " an external lazy-list library, keeping metamon's runtime dependencies\n" " limited to `gleam_stdlib`.\n" ). -opaque shrinks(EHQ) :: {shrinks, fun(() -> shrink_step(EHQ))}. -type shrink_step(EHR) :: done | {more, EHR, shrinks(EHR)}. -type tree(EHS) :: {tree, EHS, shrinks(tree(EHS))}. -file("src/metamon/generator/tree.gleam", 37). ?DOC(" An empty shrink stream.\n"). -spec no_shrinks() -> shrinks(any()). no_shrinks() -> {shrinks, fun() -> done end}. -file("src/metamon/generator/tree.gleam", 43). ?DOC( " Build a `Shrinks(a)` from a list. Useful when the shrink alternatives\n" " are statically known (e.g. integer halving).\n" ). -spec shrinks_from_list(list(EHV)) -> shrinks(EHV). shrinks_from_list(Items) -> case Items of [] -> {shrinks, fun() -> done end}; [First | Rest] -> {shrinks, fun() -> {more, First, shrinks_from_list(Rest)} end} end. -file("src/metamon/generator/tree.gleam", 52). ?DOC(" Map a function over every element of a `Shrinks(a)` lazily.\n"). -spec map_shrinks(shrinks(EHY), fun((EHY) -> EIA)) -> shrinks(EIA). map_shrinks(Stream, F) -> {shrinks, fun() -> case (erlang:element(2, Stream))() of done -> done; {more, Head, Tail} -> {more, F(Head), map_shrinks(Tail, F)} end end}. -file("src/metamon/generator/tree.gleam", 63). ?DOC( " Concatenate two `Shrinks(a)` streams. The right side is only consulted\n" " once the left is exhausted.\n" ). -spec append_shrinks(shrinks(EIC), shrinks(EIC)) -> shrinks(EIC). append_shrinks(Left, Right) -> {shrinks, fun() -> case (erlang:element(2, Left))() of done -> (erlang:element(2, Right))(); {more, Head, Tail} -> {more, Head, append_shrinks(Tail, Right)} end end}. -file("src/metamon/generator/tree.gleam", 92). ?DOC(" Force the first `n` elements of a stream into a list.\n"). -spec shrinks_take(shrinks(EIM), integer()) -> list(EIM). shrinks_take(Stream, N) -> case N =< 0 of true -> []; false -> case (erlang:element(2, Stream))() of done -> []; {more, Head, Tail} -> [Head | shrinks_take(Tail, N - 1)] end end. -file("src/metamon/generator/tree.gleam", 106). ?DOC( " Force the entire stream into a list. Use only on streams known to be\n" " finite — generator shrink streams generally are, but be careful with\n" " infinite expansions.\n" ). -spec shrinks_to_list(shrinks(EIP)) -> list(EIP). shrinks_to_list(Stream) -> case (erlang:element(2, Stream))() of done -> []; {more, Head, Tail} -> [Head | shrinks_to_list(Tail)] end. -file("src/metamon/generator/tree.gleam", 115). ?DOC( " Find the first element satisfying `predicate`, or `Error(Nil)` if no\n" " such element exists in the (finite) stream.\n" ). -spec shrinks_find(shrinks(EIS), fun((EIS) -> boolean())) -> {ok, EIS} | {error, nil}. shrinks_find(Stream, Predicate) -> case (erlang:element(2, Stream))() of done -> {error, nil}; {more, Head, Tail} -> case Predicate(Head) of true -> {ok, Head}; false -> shrinks_find(Tail, Predicate) end end. -file("src/metamon/generator/tree.gleam", 130). ?DOC(" A leaf tree with no shrink alternatives.\n"). -spec singleton(EIW) -> tree(EIW). singleton(Value) -> {tree, Value, no_shrinks()}. -file("src/metamon/generator/tree.gleam", 136). ?DOC( " Build a tree from a value and an `expand` function that produces direct\n" " child values. Each child is recursively expanded with the same function.\n" ). -spec unfold(EIY, fun((EIY) -> list(EIY))) -> tree(EIY). unfold(Value, Expand) -> {tree, Value, begin _pipe = shrinks_from_list(Expand(Value)), map_shrinks(_pipe, fun(_capture) -> unfold(_capture, Expand) end) end}. -file("src/metamon/generator/tree.gleam", 145). ?DOC(" Build a tree from a value and a pre-computed list of sub-trees.\n"). -spec from_list(EJB, list(tree(EJB))) -> tree(EJB). from_list(Value, Shrinks) -> {tree, Value, shrinks_from_list(Shrinks)}. -file("src/metamon/generator/tree.gleam", 151). ?DOC( " Map a function over every value in the tree, preserving the shrink\n" " structure.\n" ). -spec map(tree(EJF), fun((EJF) -> EJH)) -> tree(EJH). map(Tree, F) -> {tree, F(erlang:element(2, Tree)), map_shrinks( erlang:element(3, Tree), fun(_capture) -> map(_capture, F) end )}. -file("src/metamon/generator/tree.gleam", 160). ?DOC( " Monadic bind. Each value's continuation produces its own tree, and the\n" " returned tree shrinks both the outer (via the original shrinks) and\n" " the inner (via the continuation tree's shrinks). Outer shrinks come\n" " first to prefer simplifying the seed-driven structure before rerunning\n" " the continuation.\n" ). -spec bind(tree(EJJ), fun((EJJ) -> tree(EJL))) -> tree(EJL). bind(Tree, K) -> Inner = K(erlang:element(2, Tree)), Outer_shrinks = map_shrinks( erlang:element(3, Tree), fun(_capture) -> bind(_capture, K) end ), {tree, erlang:element(2, Inner), append_shrinks(Outer_shrinks, erlang:element(3, Inner))}. -file("src/metamon/generator/tree.gleam", 184). ?DOC( " Combine two trees pairwise. Used to give independent generators a clean\n" " product shrink: shrink the left first, then the right. This preserves\n" " the \"shrink one component while holding the other\" property that makes\n" " counter-examples easier to read.\n" ). -spec zip(tree(EJR), tree(EJT)) -> tree({EJR, EJT}). zip(Left, Right) -> Left_shrinks = map_shrinks( erlang:element(3, Left), fun(L) -> zip(L, Right) end ), Right_shrinks = map_shrinks( erlang:element(3, Right), fun(R) -> zip(Left, R) end ), {tree, {erlang:element(2, Left), erlang:element(2, Right)}, append_shrinks(Left_shrinks, Right_shrinks)}. -file("src/metamon/generator/tree.gleam", 195). ?DOC( " Force the tree into a list of values up to `depth` levels deep, taking\n" " at most `breadth` shrinks per level. Used for inspection in tests.\n" ). -spec outline(tree(EJW), integer(), integer()) -> list(EJW). outline(Tree, Depth, Breadth) -> case Depth =< 0 of true -> [erlang:element(2, Tree)]; false -> Direct = shrinks_take(erlang:element(3, Tree), Breadth), Nested = gleam@list:flat_map( Direct, fun(Child) -> outline(Child, Depth - 1, Breadth) end ), [erlang:element(2, Tree) | Nested] end. -file("src/metamon/generator/tree.gleam", 80). -spec filter_step(shrinks(EIJ), fun((EIJ) -> boolean())) -> shrink_step(EIJ). filter_step(Stream, Predicate) -> case (erlang:element(2, Stream))() of done -> done; {more, Head, Tail} -> case Predicate(Head) of true -> {more, Head, filter_shrinks(Tail, Predicate)}; false -> filter_step(Tail, Predicate) end end. -file("src/metamon/generator/tree.gleam", 73). ?DOC(" Lazily drop elements that fail `predicate`.\n"). -spec filter_shrinks(shrinks(EIG), fun((EIG) -> boolean())) -> shrinks(EIG). filter_shrinks(Stream, Predicate) -> {shrinks, fun() -> filter_step(Stream, Predicate) end}. -file("src/metamon/generator/tree.gleam", 172). ?DOC( " Drop tree nodes that fail `predicate`. The root is assumed to satisfy\n" " the predicate (callers must ensure this; otherwise the result has no\n" " meaningful \"current value\").\n" ). -spec filter(tree(EJO), fun((EJO) -> boolean())) -> tree(EJO). filter(Tree, Predicate) -> Kept_shrinks = begin _pipe = erlang:element(3, Tree), _pipe@1 = filter_shrinks( _pipe, fun(Child) -> Predicate(erlang:element(2, Child)) end ), map_shrinks(_pipe@1, fun(_capture) -> filter(_capture, Predicate) end) end, {tree, erlang:element(2, Tree), Kept_shrinks}.