Current section
Files
Jump to
Current section
Files
src/metamon@generator@range.erl
-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 => <<?FILEPATH/utf8>>,
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 => <<?FILEPATH/utf8>>,
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.