Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added `supervisor:which_children/1` and `supervisor:count_children/1`
- Added `monitored_by` in `process_info/2`
- Added mock implementation for `current_stacktrace` in `process_info`
- Added `erlang:list_to_bitstring`
- Reimplemented `lists:keyfind`, `lists:keymember` and `lists:member` as NIFs

### Changed

Expand Down
40 changes: 4 additions & 36 deletions libs/estdlib/src/lists.erl
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,7 @@ last([_H | T]) -> last(T).
%%-----------------------------------------------------------------------------
-spec member(E :: term(), L :: list()) -> boolean().
member(_, []) ->
false;
member(E, [E | _]) ->
true;
member(E, [_ | T]) ->
member(E, T).
erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param E the member to delete
Expand Down Expand Up @@ -265,22 +261,8 @@ keydelete(K, I, [H | T], L2) ->
%% @end
%%-----------------------------------------------------------------------------
-spec keyfind(K :: term(), I :: pos_integer(), L :: list(tuple())) -> tuple() | false.
keyfind(_K, _I, []) ->
false;
keyfind(K, I, [H | T]) when is_tuple(H) ->
case I =< tuple_size(H) of
true ->
case element(I, H) of
K ->
H;
_ ->
keyfind(K, I, T)
end;
false ->
keyfind(K, I, T)
end;
keyfind(K, I, [_H | T]) ->
keyfind(K, I, T).
keyfind(_K, _I, _L) ->
erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param K the key to match
Expand All @@ -292,21 +274,7 @@ keyfind(K, I, [_H | T]) ->
%%-----------------------------------------------------------------------------
-spec keymember(K :: term(), I :: pos_integer(), L :: list(tuple())) -> boolean().
keymember(_K, _I, []) ->
false;
keymember(K, I, [H | T]) when is_tuple(H) ->
case I =< tuple_size(H) of
true ->
case element(I, H) of
K ->
true;
_ ->
keymember(K, I, T)
end;
false ->
keymember(K, I, T)
end;
keymember(K, I, [_H | T]) ->
keymember(K, I, T).
erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param K the key to match
Expand Down
3 changes: 3 additions & 0 deletions src/libAtomVM/defaultatoms.def
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,6 @@ X(INIT_ATOM, "\x4", "init")
X(MONITORED_BY_ATOM, "\xC", "monitored_by")

X(CURRENT_STACKTRACE_ATOM, "\x12", "current_stacktrace")

X(KEY_ATOM, "\x3", "key")
X(VALUE_ATOM, "\x5", "value")
111 changes: 111 additions & 0 deletions src/libAtomVM/nifs.c
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ static term nif_erlang_setnode_2(Context *ctx, int argc, term argv[]);
static term nif_erlang_memory(Context *ctx, int argc, term argv[]);
static term nif_erlang_monitor(Context *ctx, int argc, term argv[]);
static term nif_erlang_demonitor(Context *ctx, int argc, term argv[]);
static term nif_erlang_list_to_bitstring_1(Context *ctx, int argc, term argv[]);
static term nif_erlang_unlink(Context *ctx, int argc, term argv[]);
static term nif_atomvm_add_avm_pack_binary(Context *ctx, int argc, term argv[]);
static term nif_atomvm_add_avm_pack_file(Context *ctx, int argc, term argv[]);
Expand All @@ -197,6 +198,9 @@ static term nif_code_ensure_loaded(Context *ctx, int argc, term argv[]);
static term nif_erlang_module_loaded(Context *ctx, int argc, term argv[]);
static term nif_erlang_nif_error(Context *ctx, int argc, term argv[]);
static term nif_lists_reverse(Context *ctx, int argc, term argv[]);
static term nif_lists_keyfind(Context *ctx, int argc, term argv[]);
static term nif_lists_keymember(Context *ctx, int argc, term argv[]);
static term nif_lists_member(Context *ctx, int argc, term argv[]);
static term nif_maps_from_keys(Context *ctx, int argc, term argv[]);
static term nif_maps_next(Context *ctx, int argc, term argv[]);
static term nif_unicode_characters_to_list(Context *ctx, int argc, term argv[]);
Expand Down Expand Up @@ -764,6 +768,22 @@ static const struct Nif erlang_lists_subtract_nif = {
.base.type = NIFFunctionType,
.nif_ptr = nif_erlang_lists_subtract
};
static const struct Nif lists_member_nif = {
.base.type = NIFFunctionType,
.nif_ptr = nif_lists_member
};
static const struct Nif lists_keymember_nif = {
.base.type = NIFFunctionType,
.nif_ptr = nif_lists_keymember
};
static const struct Nif lists_keyfind_nif = {
.base.type = NIFFunctionType,
.nif_ptr = nif_lists_keyfind
};
static const struct Nif list_to_bitstring_nif = {
.base.type = NIFFunctionType,
.nif_ptr = nif_erlang_list_to_bitstring_1
};
static const struct Nif zlib_compress_nif = {
.base.type = NIFFunctionType,
.nif_ptr = nif_zlib_compress_1
Expand Down Expand Up @@ -5824,6 +5844,97 @@ static term nif_erlang_lists_subtract(Context *ctx, int argc, term argv[])
return result;
}

static term nif_lists_member(Context *ctx, int argc, term argv[])
{
UNUSED(argc)
term elem = argv[0];
term list = argv[1];

while (term_is_nonempty_list(list)) {
term head = term_get_list_head(list);

TermCompareResult cmp_result = term_compare(head, elem, TermCompareExact, ctx->global);

if (cmp_result == TermEquals) {
return TRUE_ATOM;
}

if (UNLIKELY(cmp_result == TermCompareMemoryAllocFail)) {
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
}

list = term_get_list_tail(list);
}

VALIDATE_VALUE(list, term_is_nil);

return FALSE_ATOM;
}

static term nif_lists_keymember(Context *ctx, int argc, term argv[])
{
term result = nif_lists_keyfind(ctx, argc, argv);
if (UNLIKELY(term_is_invalid_term(result))) {
return result;
}
return result == FALSE_ATOM ? FALSE_ATOM : TRUE_ATOM;
}

static term nif_lists_keyfind(Context *ctx, int argc, term argv[])
{
UNUSED(argc)
term key = argv[0];
term n = argv[1];
term tuple_list = argv[2];

VALIDATE_VALUE(n, term_is_integer);

avm_int_t n_pos = term_to_int(n);

if (n_pos <= 0) {
RAISE_ERROR(BADARG_ATOM);
}

while (term_is_nonempty_list(tuple_list)) {
term tuple = term_get_list_head(tuple_list);

if (!term_is_tuple(tuple)) {
tuple_list = term_get_list_tail(tuple_list);
continue;
}

int tuple_size = term_get_tuple_arity(tuple);

if (n_pos > tuple_size) {
tuple_list = term_get_list_tail(tuple_list);
continue;
}

term nth_element = term_get_tuple_element(tuple, n_pos - 1);

TermCompareResult cmp_result = term_compare(nth_element, key, TermCompareExact, ctx->global);

if (cmp_result == TermEquals) {
return tuple;
}
if (UNLIKELY(cmp_result == TermCompareMemoryAllocFail)) {
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
}

tuple_list = term_get_list_tail(tuple_list);
}

VALIDATE_VALUE(tuple_list, term_is_nil);

return FALSE_ATOM;
}

static term nif_erlang_list_to_bitstring_1(Context *ctx, int argc, term argv[])
{
// TODO: implement proper list_to_bitstring function when the bitstrings are supported
return nif_erlang_list_to_binary_1(ctx, argc, argv);
}

#ifdef WITH_ZLIB
static term nif_zlib_compress_1(Context *ctx, int argc, term argv[])
{
Expand Down
4 changes: 4 additions & 0 deletions src/libAtomVM/nifs.gperf
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ erlang:dist_ctrl_get_data/1, &dist_ctrl_get_data_nif
erlang:dist_ctrl_put_data/2, &dist_ctrl_put_data_nif
erlang:module_loaded/1,&module_loaded_nif
erlang:nif_error/1,&nif_error_nif
erlang:list_to_bitstring/1, &list_to_bitstring_nif
erts_debug:flat_size/1, &flat_size_nif
ets:new/2, &ets_new_nif
ets:insert/2, &ets_insert_nif
Expand Down Expand Up @@ -180,6 +181,9 @@ base64:encode/1, &base64_encode_nif
base64:decode/1, &base64_decode_nif
base64:encode_to_string/1, &base64_encode_to_string_nif
base64:decode_to_string/1, &base64_decode_to_string_nif
lists:keyfind/3, &lists_keyfind_nif
lists:keymember/3, &lists_keymember_nif
lists:member/2, &lists_member_nif
lists:reverse/1, &lists_reverse_nif
lists:reverse/2, &lists_reverse_nif
maps:from_keys/2, &maps_from_keys_nif
Expand Down
11 changes: 11 additions & 0 deletions tests/erlang_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,11 @@ compile_assembler(test_op_bs_start_match_asm)
compile_erlang(test_op_bs_create_bin)
compile_assembler(test_op_bs_create_bin_asm)

compile_erlang(test_list_to_bitstring)
compile_erlang(test_lists_member)
compile_erlang(test_lists_keymember)
compile_erlang(test_lists_keyfind)

if(Erlang_VERSION VERSION_GREATER_EQUAL "23")
set(OTP23_OR_GREATER_TESTS
test_op_bs_start_match_asm.beam
Expand Down Expand Up @@ -1070,6 +1075,12 @@ add_custom_target(erlang_test_modules DEPENDS
test_ets.beam
test_node.beam


test_list_to_bitstring.beam
test_lists_member.beam
test_lists_keymember.beam
test_lists_keyfind.beam

test_op_bs_start_match.beam
test_op_bs_create_bin.beam

Expand Down
75 changes: 75 additions & 0 deletions tests/erlang_tests/test_list_to_bitstring.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
%
% This file is part of AtomVM.
%
% Copyright 2025 Franciszek Kubis <franciszek.kubis@swmansion.com>
%
% Licensed under the Apache License, Version 2.0 (the "License");
% you may not use this file except in compliance with the License.
% You may obtain a copy of the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS,
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
% See the License for the specific language governing permissions and
% limitations under the License.
%
% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
%

-module(test_list_to_bitstring).

-export([start/0, concat/2, concat2/2, compare_bin/3, id/1]).

start() ->
ok = test_concat(),
ok = test_iolist(),
ok = test_empty_list_to_binary(),
0.

test_concat() ->
Bin = concat("Hello", "world"),
Bin2 = concat2("", ""),
CompRes1 = compare_bin(Bin, <<"Hello world">>) - compare_bin(Bin, <<"HelloXworld">>),
1 = CompRes1 + byte_size(Bin2) + invalid(42),
ok.

test_iolist() ->
<<"Hello world">> = list_to_bitstring(?MODULE:id([<<"Hello ">>, [<<"wor">>, [$l, $d]]])),
ok.

test_empty_list_to_binary() ->
<<"">> = erlang:list_to_bitstring(?MODULE:id([])),
ok.

concat(A, B) ->
list_to_bitstring(?MODULE:id(A ++ " " ++ B)).

concat2(A, B) ->
list_to_bitstring(?MODULE:id(A ++ B)).

invalid(A) ->
try list_to_bitstring(?MODULE:id(A)) of
Any -> byte_size(Any)
catch
error:badarg -> 0;
_:_ -> 1000
end.

compare_bin(Bin1, Bin2) ->
compare_bin(Bin1, Bin2, byte_size(Bin1) - 1).

compare_bin(_Bin1, _Bin2, -1) ->
1;
compare_bin(Bin1, Bin2, Index) ->
B1 = binary:at(Bin1, Index),
case binary:at(Bin2, Index) of
B1 ->
compare_bin(Bin1, Bin2, Index - 1);
_Any ->
0
end.

id(X) ->
X.
Loading
Loading