Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added `binary:match/2` and `binary:match/3`
- Added `supervisor:which_children/1`
- Added `monitored_by` in `process_info/2`
- Added `lists:keyfind`, `lists:keymember`, `lists:keysearch`, `lists:member` and `erlang:list_to_bitstring`

### Changed

Expand Down
50 changes: 14 additions & 36 deletions libs/estdlib/src/lists.erl
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
foreach/2,
keydelete/3,
keyfind/3,
keysearch/3,
keymember/3,
keyreplace/4,
keysort/2,
Expand Down Expand Up @@ -135,11 +136,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 @@ -256,6 +253,15 @@ keydelete(K, I, [H | T], L2) when is_tuple(H) ->
keydelete(K, I, [H | T], L2) ->
keydelete(K, I, T, [H | L2]).

-spec keysearch(Key, N, TupleList) -> {value, Tuple} | false when
Key :: term(),
N :: pos_integer(),
TupleList :: [Tuple],
Tuple :: tuple().

keysearch(_, _, _) ->
erlang:nif_error(undef).

%%-----------------------------------------------------------------------------
%% @param K the key to match
%% @param I the position in the tuple to compare (1..tuple_size)
Expand All @@ -265,22 +271,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 +284,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 @@ -192,3 +192,6 @@ X(NOMATCH_ATOM, "\x7", "nomatch")
X(INIT_ATOM, "\x4", "init")

X(MONITORED_BY_ATOM, "\xC", "monitored_by")

X(KEY_ATOM, "\x3", "key")
X(VALUE_ATOM, "\x5", "value")
138 changes: 138 additions & 0 deletions src/libAtomVM/nifs.c
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,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 @@ -196,13 +197,18 @@ 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_keysearch(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[]);
static term nif_unicode_characters_to_binary(Context *ctx, int argc, term argv[]);
static term nif_erlang_lists_subtract(Context *ctx, int argc, term argv[]);
static term nif_zlib_compress_1(Context *ctx, int argc, term argv[]);


#define DECLARE_MATH_NIF_FUN(moniker) \
static term nif_math_##moniker(Context *ctx, int argc, term argv[]);

Expand Down Expand Up @@ -868,6 +874,26 @@ 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 lists_keysearch_nif = {
.base.type = NIFFunctionType,
.nif_ptr = nif_lists_keysearch
};
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,
Expand Down Expand Up @@ -5842,6 +5868,118 @@ 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];
VALIDATE_VALUE(list, term_is_list);

int proper;
term_list_length(list, &proper);
if (UNLIKELY(!proper)) {
RAISE_ERROR(BADARG_ATOM);
}

while (!term_is_nil(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);
}

return FALSE_ATOM;
}

static term nif_lists_keymember(Context *ctx, int argc, term argv[])
{
term result = nif_lists_keyfind(ctx, argc, argv);
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];

if (!term_is_integer(n) || !term_is_list(tuple_list)) {
RAISE_ERROR(BADARG_ATOM);
}

int n_pos = term_to_int(n);

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

int proper;
term_list_length(tuple_list, &proper);
if (UNLIKELY(!proper)) {
RAISE_ERROR(BADARG_ATOM);
}

while (!term_is_nil(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);
}

return FALSE_ATOM;
}

static term nif_lists_keysearch(Context *ctx, int argc, term argv[])
{
term found_value = nif_lists_keyfind(ctx, argc, argv);
if (UNLIKELY(memory_ensure_free_with_roots(ctx, TUPLE_SIZE(2), 1, &found_value, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
}
term result_tuple = term_alloc_tuple(2, &ctx->heap);
term_put_tuple_element(result_tuple, 0, VALUE_ATOM);
term_put_tuple_element(result_tuple, 1, found_value);
return found_value == FALSE_ATOM ? FALSE_ATOM : result_tuple;
}

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
5 changes: 5 additions & 0 deletions src/libAtomVM/nifs.gperf
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,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 @@ -179,6 +180,10 @@ 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:keysearch/3, &lists_keysearch_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
13 changes: 13 additions & 0 deletions tests/erlang_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,12 @@ 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)
compile_erlang(test_lists_keysearch)

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 +1076,13 @@ 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_lists_keysearch.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 <[email protected]>
%
% 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