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", 87).
?DOC(" The shrink target for this range.\n").
-spec origin(range()) -> integer().
origin(Range) ->
erlang:element(2, Range).
-file("src/metamon/generator/range.gleam", 119).
?DOC(
" Normalise a pair of bounds so that `lo <= hi`. Inverted pairs are\n"
" swapped silently to match the convention `generator.float` uses;\n"
" equal pairs pass through unchanged.\n"
).
-spec order_bounds(integer(), integer()) -> {integer(), integer()}.
order_bounds(Lo, Hi) ->
case Lo > Hi of
true ->
{Hi, Lo};
false ->
{Lo, Hi}
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"
" Inverted bounds (`lo > hi`) are auto-swapped to match the lenient\n"
" policy `generator.float` already uses. Use `singleton` when both\n"
" values are intentionally equal.\n"
).
-spec constant(integer(), integer()) -> range().
constant(Lo, Hi) ->
{Lo@1, Hi@1} = order_bounds(Lo, Hi),
Chosen_origin = case (Lo@1 =< 0) andalso (0 =< Hi@1) of
true ->
0;
false ->
Lo@1
end,
{range, Chosen_origin, {const, Lo@1, Hi@1}}.
-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"
" Inverted bounds (`lo > hi`) are auto-swapped.\n"
).
-spec linear(integer(), integer()) -> range().
linear(Lo, Hi) ->
{Lo@1, Hi@1} = order_bounds(Lo, Hi),
Chosen_origin = case (Lo@1 =< 0) andalso (0 =< Hi@1) of
true ->
0;
false ->
Lo@1
end,
{range, Chosen_origin, {linear, Lo@1, Hi@1}}.
-file("src/metamon/generator/range.gleam", 77).
?DOC(
" Exponential scaling: bounds grow roughly like `size^2 / max_size`,\n"
" well-suited for ranges spanning many orders of magnitude.\n"
"\n"
" Inverted bounds (`lo > hi`) are auto-swapped.\n"
).
-spec exponential(integer(), integer()) -> range().
exponential(Lo, Hi) ->
{Lo@1, Hi@1} = order_bounds(Lo, Hi),
Chosen_origin = case (Lo@1 =< 0) andalso (0 =< Hi@1) of
true ->
0;
false ->
Lo@1
end,
{range, Chosen_origin, {exponential, Lo@1, Hi@1}}.
-file("src/metamon/generator/range.gleam", 126).
-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 => 129});
false ->
nil
end.
-file("src/metamon/generator/range.gleam", 67).
?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"
" Inverted bounds (`lo > hi`) are auto-swapped. After that swap, the\n"
" function still panics if `origin` is outside the resulting\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) ->
{Lo@1, Hi@1} = order_bounds(Lo, Hi),
_ = assert_origin_in_bounds(Origin, Lo@1, Hi@1),
{range, Origin, {linear, Lo@1, Hi@1}}.
-file("src/metamon/generator/range.gleam", 134).
-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", 142).
-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", 147).
-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", 93).
?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.