-module(metamon@generator@range). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/metamon/generator/range.gleam"). -export([singleton/1, origin/1, constant/2, linear/2, exponential/2, linear_from/3, bounds/3]). -export_type([kind/0, range/0]). -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( " Bounds + shrink origin + size scaling for numeric generators.\n" "\n" " A `Range(Int)` describes how a generator's bounds widen as the test\n" " `size` parameter grows from 0 (smallest) toward `max_size`, and where\n" " shrinking should converge (\"origin\"). Modelled on Hedgehog's `Range`.\n" ). -type kind() :: {const, integer(), integer()} | {linear, integer(), integer()} | {exponential, integer(), integer()}. -opaque range() :: {range, integer(), kind()}. -file("src/metamon/generator/range.gleam", 24). ?DOC(" A range that always returns exactly `value`.\n"). -spec singleton(integer()) -> range(). singleton(Value) -> {range, Value, {const, Value, Value}}. -file("src/metamon/generator/range.gleam", 86). ?DOC(" The shrink target for this range.\n"). -spec origin(range()) -> integer(). origin(Range) -> erlang:element(2, Range). -file("src/metamon/generator/range.gleam", 115). -spec assert_ordered(integer(), integer()) -> nil. assert_ordered(Lo, Hi) -> case Lo > Hi of true -> erlang:error(#{gleam_error => panic, message => <<"range: lo must be <= hi (got inverted bounds)"/utf8>>, file => <>, module => <<"metamon/generator/range"/utf8>>, function => <<"assert_ordered"/utf8>>, line => 117}); false -> nil end. -file("src/metamon/generator/range.gleam", 34). ?DOC( " A range that ignores `size` and always uses `[lo, hi]`. Origin is `lo`\n" " (or `0` if `0` lies inside the interval, which usually shrinks better).\n" "\n" " Panics at construction if `lo > hi`. An inverted pair is almost always\n" " a bug (swapped arguments), so failing visibly catches it earlier than\n" " silently normalising.\n" ). -spec constant(integer(), integer()) -> range(). constant(Lo, Hi) -> _ = assert_ordered(Lo, Hi), Chosen_origin = case (Lo =< 0) andalso (0 =< Hi) of true -> 0; false -> Lo end, {range, Chosen_origin, {const, Lo, Hi}}. -file("src/metamon/generator/range.gleam", 47). ?DOC( " A range that scales linearly: at `size = 0` returns `[origin, origin]`\n" " and at `size = max_size` returns the full `[lo, hi]`.\n" "\n" " Panics at construction if `lo > hi`.\n" ). -spec linear(integer(), integer()) -> range(). linear(Lo, Hi) -> _ = assert_ordered(Lo, Hi), Chosen_origin = case (Lo =< 0) andalso (0 =< Hi) of true -> 0; false -> Lo end, {range, Chosen_origin, {linear, Lo, Hi}}. -file("src/metamon/generator/range.gleam", 76). ?DOC( " Exponential scaling: bounds grow roughly like `size^2 / max_size`,\n" " well-suited for ranges spanning many orders of magnitude.\n" "\n" " Panics at construction if `lo > hi`.\n" ). -spec exponential(integer(), integer()) -> range(). exponential(Lo, Hi) -> _ = assert_ordered(Lo, Hi), Chosen_origin = case (Lo =< 0) andalso (0 =< Hi) of true -> 0; false -> Lo end, {range, Chosen_origin, {exponential, Lo, Hi}}. -file("src/metamon/generator/range.gleam", 122). -spec assert_origin_in_bounds(integer(), integer(), integer()) -> nil. assert_origin_in_bounds(Origin, Lo, Hi) -> case (Origin < Lo) orelse (Origin > Hi) of true -> erlang:error(#{gleam_error => panic, message => <<"range.linear_from: origin must lie inside [lo, hi] (an out-of-range origin would emit values outside the documented interval at small sizes)"/utf8>>, file => <>, module => <<"metamon/generator/range"/utf8>>, function => <<"assert_origin_in_bounds"/utf8>>, line => 125}); false -> nil end. -file("src/metamon/generator/range.gleam", 66). ?DOC( " Like `linear` but the origin is supplied explicitly. Useful when the\n" " natural shrink target is not `0` (e.g. years near `2000` shrinking to\n" " `2000`).\n" "\n" " Panics at construction if `lo > hi` or if `origin` is outside\n" " `[lo, hi]`. An origin outside the interval would silently emit\n" " out-of-range values at small sizes (`size = 0` collapses both\n" " bounds to `origin`), so failing visibly catches the misuse. This\n" " matches the invariant `linear` upholds automatically (its origin\n" " is always either `0` when inside the interval, or `lo`).\n" ). -spec linear_from(integer(), integer(), integer()) -> range(). linear_from(Origin, Lo, Hi) -> _ = assert_ordered(Lo, Hi), _ = assert_origin_in_bounds(Origin, Lo, Hi), {range, Origin, {linear, Lo, Hi}}. -file("src/metamon/generator/range.gleam", 130). -spec clamp(integer(), integer(), integer()) -> integer(). clamp(Value, Lo, Hi) -> case {Value < Lo, Value > Hi} of {true, _} -> Lo; {_, true} -> Hi; {_, _} -> Value end. -file("src/metamon/generator/range.gleam", 138). -spec scale_linear(integer(), integer(), integer(), integer()) -> integer(). scale_linear(Origin, Bound, Size, Max_size) -> Delta = Bound - Origin, Origin + (case Max_size of 0 -> 0; Gleam@denominator -> Delta * Size div Gleam@denominator end). -file("src/metamon/generator/range.gleam", 143). -spec scale_exponential(integer(), integer(), integer(), integer()) -> integer(). scale_exponential(Origin, Bound, Size, Max_size) -> Delta = Bound - Origin, Origin + (case (Max_size * Max_size) of 0 -> 0; Gleam@denominator -> (Delta * Size) * Size div Gleam@denominator end). -file("src/metamon/generator/range.gleam", 92). ?DOC( " Compute the actual `[lo, hi]` bounds for a given `size` between `0`\n" " and `max_size`. `size` is clamped into `[0, max_size]`.\n" ). -spec bounds(range(), integer(), integer()) -> {integer(), integer()}. bounds(Range, Size, Max_size) -> Max_size@1 = case Max_size =< 0 of true -> 1; false -> Max_size end, Clamped_size = clamp(Size, 0, Max_size@1), case erlang:element(3, Range) of {const, Lo, Hi} -> {Lo, Hi}; {linear, Lo@1, Hi@1} -> Lo_scaled = scale_linear( erlang:element(2, Range), Lo@1, Clamped_size, Max_size@1 ), Hi_scaled = scale_linear( erlang:element(2, Range), Hi@1, Clamped_size, Max_size@1 ), {Lo_scaled, Hi_scaled}; {exponential, Lo@2, Hi@2} -> Lo_scaled@1 = scale_exponential( erlang:element(2, Range), Lo@2, Clamped_size, Max_size@1 ), Hi_scaled@1 = scale_exponential( erlang:element(2, Range), Hi@2, Clamped_size, Max_size@1 ), {Lo_scaled@1, Hi_scaled@1} end.