Current section

Files

Jump to
ra src ra_range.erl
Raw

src/ra_range.erl

%% This Source Code Form is subject to the terms of the Mozilla Public
%% License, v. 2.0. If a copy of the MPL was not distributed with this
%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
%%
%% Copyright (c) 2017-2025 Broadcom. All Rights Reserved. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries.
%%
%% @hidden
-module(ra_range).
-export([
new/1,
new/2,
add/2,
combine/2,
in/2,
extend/2,
limit/2,
truncate/2,
size/1,
overlap/2,
subtract/2,
fold/3
]).
-type range() :: undefined | {ra:index(), ra:index()}.
-export_type([range/0]).
-define(IS_RANGE(R), ((is_tuple(R) andalso
tuple_size(R) == 2 andalso
is_integer(element(1, R)) andalso
is_integer(element(2, R))) orelse
R == undefined)).
-spec new(ra:index()) -> range().
new(Start) when is_integer(Start) ->
{Start, Start}.
-spec new(ra:index(), ra:index()) -> range().
new(Start, End)
when is_integer(Start) andalso
is_integer(End) andalso
Start =< End ->
{Start, End};
new(Start, End)
when is_integer(Start) andalso
is_integer(End) ->
undefined.
-spec add(AddRange :: range(), CurRange :: range()) -> range().
add(undefined, Range) ->
Range;
add({AddStart, AddEnd}, {Start, End})
when Start =< AddEnd + 1 andalso
End + 1 >= AddStart ->
{min(AddStart, Start), max(AddEnd, End)};
add(AddRange, _Range) ->
%% no overlap, return add range
AddRange.
%% @doc Combines two ranges taking the wider start but clamping the upper bound
%% to AddRange. This is used when AddRange represents newer data that
%% overwrites the tail of CurRange.
-spec combine(AddRange :: range(), CurRange :: range()) -> range().
combine(undefined, Range) ->
Range;
combine({AddStart, AddEnd}, {Start, _End}) ->
{min(AddStart, Start), AddEnd};
combine(AddRange, _Range) ->
AddRange.
-spec in(ra:index(), range()) -> boolean().
in(_Idx, undefined) ->
false;
in(Idx, {Start, End}) ->
Idx >= Start andalso Idx =< End.
-spec limit(ra:index(), range()) -> range().
limit(CeilExcl, {Start, _End})
when is_integer(CeilExcl) andalso
CeilExcl =< Start ->
undefined;
limit(CeilExcl, {Start, End})
when is_integer(CeilExcl) andalso
CeilExcl =< End ->
{Start, CeilExcl - 1};
limit(CeilExcl, Range)
when is_integer(CeilExcl) ->
Range.
-spec truncate(ra:index(), range()) -> range().
truncate(UpToIncl, {_Start, End})
when is_integer(UpToIncl) andalso
is_integer(End) andalso
UpToIncl >= End ->
undefined;
truncate(UpToIncl, {Start, End})
when is_integer(UpToIncl) andalso
is_integer(Start) andalso
UpToIncl >= Start ->
{UpToIncl + 1, End};
truncate(UpToIncl, Range)
when is_integer(UpToIncl) andalso
?IS_RANGE(Range) ->
Range.
size(undefined) ->
0;
size({Start, End}) ->
End - Start + 1.
-spec extend(ra:index(), range()) -> range().
extend(Idx, {Start, End})
when Idx == End + 1 ->
{Start, Idx};
extend(Idx, undefined) when is_integer(Idx) ->
ra_range:new(Idx);
extend(Idx, Range) ->
error({cannot_extend, Idx, Range}).
-spec overlap(range(), range()) -> range().
overlap({ReqStart, ReqEnd}, {Start, End}) ->
new(max(ReqStart, Start), min(ReqEnd, End));
overlap(_Range1, _Range2) ->
undefined.
%% @doc subtracts the range in the first argument
%% from that of the range in the second arg.
%% Returns a list of remaining ranges
%% @end
-spec subtract(range(), range()) -> [range()].
subtract(_Range1, undefined) ->
[];
subtract(undefined, Range) ->
[Range];
subtract({_SubStart, _SubEnd} = SubRange, {Start, End} = Range) ->
case overlap(SubRange, Range) of
undefined ->
[Range];
{OStart, OEnd} ->
[R || {_, _} = R <- [new(Start, OStart -1),
new(OEnd + 1, End)]]
end.
-spec fold(range(), fun((ra:index(), Acc) -> Acc), Acc) ->
Acc when Acc :: term().
fold(undefined, _Fun, Acc) ->
Acc;
fold({S, E}, Fun, Acc) ->
fold0(S, E, Fun, Acc).
%% internals
fold0(S, S, Fun, Acc) ->
Fun(S, Acc);
fold0(S, E, Fun, Acc) ->
fold0(S+1, E, Fun, Fun(S, Acc)).
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
subtract_test() ->
?assertEqual([], subtract({1, 10}, undefined)),
?assertEqual([{1, 10}], subtract(undefined, {1, 10})),
?assertEqual([], subtract({1, 10}, {1, 10})),
?assertEqual([], subtract({1, 10}, {4, 6})),
?assertEqual([{4, 6}], subtract({8, 10}, {4, 6})),
?assertEqual([{11, 20}], subtract({1, 10}, {1, 20})),
?assertEqual([{1, 1}, {11, 20}], subtract({2, 10}, {1, 20})),
?assertEqual([{1, 9}], subtract({10, 20}, {1, 20})),
ok.
overlap_test() ->
?assertEqual(undefined, overlap({1, 10}, undefined)),
?assertEqual(undefined, overlap(undefined, {1, 10})),
?assertEqual(undefined, overlap({1, 10}, {11, 15})),
?assertEqual(undefined, overlap({16, 20}, {11, 15})),
?assertEqual({11, 11}, overlap({1, 11}, {11, 15})),
?assertEqual({14, 15}, overlap({14, 20}, {11, 15})),
?assertEqual({12, 14}, overlap({12, 14}, {11, 15})),
?assertEqual({11, 15}, overlap({1, 20}, {11, 15})),
ok.
add_test() ->
?assertEqual(undefined, add(undefined, undefined)),
?assertEqual({1, 10}, add(undefined, {1, 10})),
?assertEqual({1, 10}, add({1, 10}, undefined)),
?assertEqual({1, 20}, add({1, 10}, {11, 20})),
?assertEqual({1, 20}, add({1, 10}, {5, 20})),
?assertEqual({1, 10}, add({1, 10}, {5, 9})),
?assertEqual({5, 10}, add({6, 10}, {5, 9})),
?assertEqual({1, 10}, add({6, 10}, {1, 5})),
%% when the add range is smaller than the prior range
%% return the additional range
?assertEqual({1, 3}, add({1, 3}, {6, 10})),
?assertEqual({1, 10}, add({6, 10}, {1, 7})),
ok.
truncate_test() ->
?assertEqual(undefined, truncate(9, undefined)),
?assertEqual({6, 10}, truncate(5, {1, 10})),
?assertEqual(undefined, truncate(11, {1, 10})),
?assertEqual({10, 20}, truncate(9, {10, 20})),
ok.
limit_test() ->
?assertEqual(undefined, limit(9, undefined)),
?assertEqual({1, 4}, limit(5, {1, 10})),
?assertEqual({1, 10}, limit(11, {1, 10})),
?assertEqual(undefined, limit(10, {10, 20})),
?assertEqual(undefined, limit(1, {10, 20})),
ok.
in_test() ->
?assertEqual(false, in(9, undefined)),
?assertEqual(true, in(5, {1, 10})),
?assertEqual(true, in(10, {1, 10})),
?assertEqual(true, in(1, {1, 10})),
?assertEqual(false, in(11, {1, 10})),
?assertEqual(false, in(0, {1, 10})),
ok.
extend_test() ->
?assertEqual({9, 9}, extend(9, undefined)),
?assertEqual({1, 11}, extend(11, {1, 10})),
?assertError({cannot_extend, 1, {5, 10}}, extend(1, {5, 10})),
?assertError({cannot_extend, 12, {1, 10}}, extend(12, {1, 10})),
ok.
fold_test() ->
?assertEqual([4,3,2,1], fold({1, 4}, fun ra_lib:cons/2, [])),
?assertEqual([], fold(undefined, fun ra_lib:cons/2, [])),
ok.
combine_test() ->
?assertEqual(undefined, combine(undefined, undefined)),
?assertEqual({1, 2}, combine({1, 2}, undefined)),
?assertEqual({1, 2}, combine(undefined, {1, 2})),
?assertEqual({1, 5}, combine({1, 5}, {1, 2})),
?assertEqual({1, 5}, combine({4, 5}, {1, 2})),
%% the add range has a lower end, in this case it should truncate the
%% result to the add
?assertEqual({1, 3}, combine({2, 3}, {1, 5})),
ok.
-endif.