Skip to content

Commit eadb7da

Browse files
committed
Forward port changes from v0.6 release branch
Merge from release-0.6: - Implement erlang:nif_error/1 (#1768) - Replace quick_sort with merge_sort for lists (#1767) - Fix lists:seq/2,3 when they should return an empty list (#1766)
2 parents afec5ec + 93c6a97 commit eadb7da

File tree

5 files changed

+108
-11
lines changed

5 files changed

+108
-11
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7474
### Added
7575

7676
- Added `lists:keysort/2`
77+
- Added `lists:merge/2,3`
7778

7879
### Fixed
7980

8081
- Fixed a bug where binary matching could fail due to a missing preservation of the matched binary.
82+
- Fixed a bug where `lists:seq/2` wouldn't return the empty list in valid cases.
83+
84+
### Changed
85+
86+
- lists sort function now use a stable merge sort implementation instead of quick sort
8187

8288
## [0.6.6] - 2025-06-23
8389

libs/estdlib/src/lists.erl

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
join/2,
6161
seq/2, seq/3,
6262
sort/1, sort/2,
63+
merge/2, merge/3,
6364
split/2,
6465
usort/1, usort/2,
6566
dropwhile/2,
@@ -622,7 +623,9 @@ join_1(_Sep, []) ->
622623
%%-----------------------------------------------------------------------------
623624
-spec seq(From :: integer(), To :: integer()) -> list().
624625
seq(From, To) when is_integer(From) andalso is_integer(To) andalso From =< To ->
625-
seq_r(From, To, 1, []).
626+
seq_r(From, To, 1, []);
627+
seq(From, To) when is_integer(From) andalso is_integer(To) andalso From =:= To + 1 ->
628+
[].
626629

627630
%%-----------------------------------------------------------------------------
628631
%% @param From from integer
@@ -644,6 +647,8 @@ seq(From, To, Incr) when
644647
error(badarg);
645648
seq(To, To, 0) ->
646649
[To];
650+
seq(From, To, Incr) when From =:= To + Incr ->
651+
[];
647652
seq(From, To, Incr) ->
648653
Last = From + ((To - From) div Incr) * Incr,
649654
seq_r(From, Last, Incr, []).
@@ -672,8 +677,23 @@ sort(List) when is_list(List) ->
672677
%% @end
673678
%%-----------------------------------------------------------------------------
674679
-spec sort(Fun :: fun((T, T) -> boolean()), List :: [T]) -> [T].
675-
sort(Fun, List) when is_function(Fun), is_list(List) ->
676-
quick_sort(Fun, List).
680+
sort(Fun, List) when is_function(Fun, 2), is_list(List) ->
681+
merge_sort(Fun, List).
682+
683+
merge_sort(_Fun, []) ->
684+
[];
685+
merge_sort(_Fun, [_] = L) ->
686+
L;
687+
merge_sort(Fun, List) ->
688+
{H1, H2} = merge_sort_split(List, List, []),
689+
merge(Fun, merge_sort(Fun, H1), merge_sort(Fun, H2), []).
690+
691+
merge_sort_split([], Half1, Half2) ->
692+
{lists:reverse(Half2), Half1};
693+
merge_sort_split([_], Half1, Half2) ->
694+
{lists:reverse(Half2), Half1};
695+
merge_sort_split([_, _ | T], [H | Half1T], Half2) ->
696+
merge_sort_split(T, Half1T, [H | Half2]).
677697

678698
%%-----------------------------------------------------------------------------
679699
%% @param N elements non negative Integer
@@ -708,14 +728,39 @@ split(N, [H | T], R) ->
708728
split(_, [], _) ->
709729
badarg.
710730

711-
%% Attribution: https://erlang.org/doc/programming_examples/list_comprehensions.html#quick-sort
712-
%% @private
713-
quick_sort(Fun, [Pivot | T]) ->
714-
quick_sort(Fun, [X || X <- T, Fun(X, Pivot)]) ++
715-
[Pivot] ++
716-
quick_sort(Fun, [X || X <- T, not Fun(X, Pivot)]);
717-
quick_sort(_Fun, []) ->
718-
[].
731+
%%-----------------------------------------------------------------------------
732+
%% @param List1 first list to merge, previously sorted
733+
%% @param List2 second list to merge, previously sorted
734+
%% @returns Merged list of List1 and List2
735+
%% @doc Returns a list formed by merging List1 and List2, following natural
736+
%% order. If elements compare equal, element from List1 is picked first
737+
%% @end
738+
%%-----------------------------------------------------------------------------
739+
merge(List1, List2) ->
740+
merge(fun lt/2, List1, List2, []).
741+
742+
%%-----------------------------------------------------------------------------
743+
%% @param Fun ordering function
744+
%% @param List1 first list to merge, previously sorted
745+
%% @param List2 second list to merge, previously sorted
746+
%% @returns Merged list of List1 and List2
747+
%% @doc Returns a list formed by merging List1 and List2, following Fun
748+
%% order. If elements compare equal, element from List1 is picked first
749+
%% @end
750+
%%-----------------------------------------------------------------------------
751+
merge(Fun, List1, List2) ->
752+
merge(Fun, List1, List2, []).
753+
754+
merge(_Fun, [], Right, Acc) ->
755+
lists:reverse(Acc, Right);
756+
merge(_Fun, Left, [], Acc) ->
757+
lists:reverse(Acc, Left);
758+
merge(Fun, [A | As], [B | Bs], Acc) ->
759+
% keep sort stable, if B < A, put it first, otherwise keep A first
760+
case Fun(B, A) of
761+
true -> merge(Fun, [A | As], Bs, [B | Acc]);
762+
false -> merge(Fun, As, [B | Bs], [A | Acc])
763+
end.
719764

720765
%% @private
721766
lt(A, B) -> A < B.

src/libAtomVM/nifs.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ static term nif_code_load_abs(Context *ctx, int argc, term argv[]);
194194
static term nif_code_load_binary(Context *ctx, int argc, term argv[]);
195195
static term nif_code_ensure_loaded(Context *ctx, int argc, term argv[]);
196196
static term nif_erlang_module_loaded(Context *ctx, int argc, term argv[]);
197+
static term nif_erlang_nif_error(Context *ctx, int argc, term argv[]);
197198
static term nif_lists_reverse(Context *ctx, int argc, term argv[]);
198199
static term nif_maps_from_keys(Context *ctx, int argc, term argv[]);
199200
static term nif_maps_next(Context *ctx, int argc, term argv[]);
@@ -831,6 +832,12 @@ static const struct Nif module_loaded_nif =
831832
.nif_ptr = nif_erlang_module_loaded
832833
};
833834

835+
static const struct Nif nif_error_nif =
836+
{
837+
.base.type = NIFFunctionType,
838+
.nif_ptr = nif_erlang_nif_error
839+
};
840+
834841
static const struct Nif lists_reverse_nif =
835842
{
836843
.base.type = NIFFunctionType,
@@ -5414,6 +5421,14 @@ static term nif_erlang_module_loaded(Context *ctx, int argc, term argv[])
54145421
return module != NULL ? TRUE_ATOM : FALSE_ATOM;
54155422
}
54165423

5424+
static term nif_erlang_nif_error(Context *ctx, int argc, term argv[])
5425+
{
5426+
UNUSED(argc);
5427+
UNUSED(argv);
5428+
5429+
RAISE_ERROR(UNDEF_ATOM);
5430+
}
5431+
54175432
static term nif_lists_reverse(Context *ctx, int argc, term argv[])
54185433
{
54195434
// Compared to erlang version, compute the length of the list and allocate

src/libAtomVM/nifs.gperf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ erlang:dist_ctrl_get_data_notification/1, &dist_ctrl_get_data_notification_nif
138138
erlang:dist_ctrl_get_data/1, &dist_ctrl_get_data_nif
139139
erlang:dist_ctrl_put_data/2, &dist_ctrl_put_data_nif
140140
erlang:module_loaded/1,&module_loaded_nif
141+
erlang:nif_error/1,&nif_error_nif
141142
erts_debug:flat_size/1, &flat_size_nif
142143
ets:new/2, &ets_new_nif
143144
ets:insert/2, &ets_insert_nif

tests/libs/estdlib/test_lists.erl

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,34 @@ test_keysort() ->
133133
?ASSERT_MATCH(lists:keysort(2, [{foo, 2}, {bar, 1}]), [{bar, 1}, {foo, 2}]),
134134
?ASSERT_ERROR(lists:keysort(1, [1, 2])),
135135
?ASSERT_ERROR(lists:keysort(3, [{1, bar}, {2, foo}])),
136+
137+
% Our sort is always stable, but older versions of OTP only have
138+
% keysort/2 documented as stable
139+
?ASSERT_MATCH(
140+
lists:keysort(1, [
141+
{3, a}, {2, c}, {1, z}, {2, b}, {2, a}
142+
]),
143+
[{1, z}, {2, c}, {2, b}, {2, a}, {3, a}]
144+
),
145+
?ASSERT_MATCH(
146+
lists:keysort(1, [
147+
{3, a}, {1, z}, {2, c}, {2, b}, {2, a}
148+
]),
149+
[{1, z}, {2, c}, {2, b}, {2, a}, {3, a}]
150+
),
151+
?ASSERT_MATCH(
152+
lists:keysort(1, [
153+
{3, a}, {2, c}, {2, b}, {1, z}, {2, a}
154+
]),
155+
[{1, z}, {2, c}, {2, b}, {2, a}, {3, a}]
156+
),
157+
?ASSERT_MATCH(
158+
lists:keysort(1, [
159+
{3, a}, {2, c}, {2, b}, {2, a}, {1, z}
160+
]),
161+
[{1, z}, {2, c}, {2, b}, {2, a}, {3, a}]
162+
),
163+
136164
ok.
137165

138166
test_keystore() ->
@@ -287,6 +315,8 @@ test_seq() ->
287315
?ASSERT_MATCH(lists:seq(5, 1, -1), [5, 4, 3, 2, 1]),
288316
?ASSERT_MATCH(lists:seq(1, 1, 0), [1]),
289317
?ASSERT_MATCH(lists:seq(1, 1), [1]),
318+
?ASSERT_MATCH(lists:seq(1, 0), []),
319+
?ASSERT_MATCH(lists:seq(1, 0, 1), []),
290320

291321
?ASSERT_ERROR(lists:seq(foo, 1)),
292322
?ASSERT_ERROR(lists:seq(1, bar)),

0 commit comments

Comments
 (0)