From 736aa51ffce6cd9ff73867a1474b1d115bd81f40 Mon Sep 17 00:00:00 2001 From: Jakub Gonet Date: Fri, 11 Oct 2024 12:11:36 +0200 Subject: [PATCH] Add support for list_to_integer/2 --- libs/estdlib/src/erlang.erl | 14 ++++++++ src/libAtomVM/nifs.c | 38 ++++++++++++++++----- src/libAtomVM/nifs.gperf | 1 + tests/erlang_tests/test_list_to_integer.erl | 18 +++++++--- 4 files changed, 57 insertions(+), 14 deletions(-) diff --git a/libs/estdlib/src/erlang.erl b/libs/estdlib/src/erlang.erl index 8d41689b6..1ea0709ba 100644 --- a/libs/estdlib/src/erlang.erl +++ b/libs/estdlib/src/erlang.erl @@ -52,6 +52,7 @@ list_to_existing_atom/1, list_to_binary/1, list_to_integer/1, + list_to_integer/2, list_to_tuple/1, iolist_to_binary/1, binary_to_atom/1, @@ -600,6 +601,19 @@ list_to_binary(_IOList) -> list_to_integer(_String) -> erlang:nif_error(undefined). +%%----------------------------------------------------------------------------- +%% @param String string to convert to integer +%% @param Base string to convert to integer +%% @returns an integer value from its string representation +%% @doc Convert a string (list of characters) to integer in specified base. +%% Errors with `badarg' if the string is not a representation of an integer or +%% the base is out of bounds. +%% @end +%%----------------------------------------------------------------------------- +-spec list_to_integer(String :: string(), Base :: 2..36) -> integer(). +list_to_integer(_String, _Base) -> + erlang:nif_error(undefined). + %%----------------------------------------------------------------------------- %% @param List list to convert to tuple %% @returns a tuple with elements of the list diff --git a/src/libAtomVM/nifs.c b/src/libAtomVM/nifs.c index fedd46be2..2a02ed107 100644 --- a/src/libAtomVM/nifs.c +++ b/src/libAtomVM/nifs.c @@ -120,7 +120,7 @@ static term nif_erlang_link(Context *ctx, int argc, term argv[]); static term nif_erlang_float_to_binary(Context *ctx, int argc, term argv[]); static term nif_erlang_float_to_list(Context *ctx, int argc, term argv[]); static term nif_erlang_list_to_binary_1(Context *ctx, int argc, term argv[]); -static term nif_erlang_list_to_integer_1(Context *ctx, int argc, term argv[]); +static term nif_erlang_list_to_integer(Context *ctx, int argc, term argv[]); static term nif_erlang_list_to_float_1(Context *ctx, int argc, term argv[]); static term nif_erlang_list_to_atom_1(Context *ctx, int argc, term argv[]); static term nif_erlang_list_to_existing_atom_1(Context *ctx, int argc, term argv[]); @@ -386,7 +386,7 @@ static const struct Nif list_to_binary_nif = static const struct Nif list_to_integer_nif = { .base.type = NIFFunctionType, - .nif_ptr = nif_erlang_list_to_integer_1 + .nif_ptr = nif_erlang_list_to_integer }; static const struct Nif list_to_float_nif = @@ -2544,9 +2544,30 @@ static term nif_erlang_list_to_binary_1(Context *ctx, int argc, term argv[]) return bin_res; } -static term nif_erlang_list_to_integer_1(Context *ctx, int argc, term argv[]) +static avm_int_t to_digit_index(avm_int_t character) { - UNUSED(argc); + if (character >= '0' && character <= '9') { + return character - '0'; + } else if (character >= 'a' && character <= 'z') { + return character - 'a' + 10; + } else if (character >= 'A' && character <= 'Z') { + return character - 'A' + 10; + } else { + return -1; + } +} + +static term nif_erlang_list_to_integer(Context *ctx, int argc, term argv[]) +{ + avm_int_t base = 10; + if(argc == 2) { + term t = argv[1]; + VALIDATE_VALUE(t, term_is_integer); + base = term_to_int(t); + if (UNLIKELY(base < 2 || base > 36)) { + RAISE_ERROR(BADARG_ATOM); + } + } term t = argv[0]; int64_t acc = 0; @@ -2565,22 +2586,21 @@ static term nif_erlang_list_to_integer_1(Context *ctx, int argc, term argv[]) while (term_is_nonempty_list(t)) { term head = term_get_list_head(t); - VALIDATE_VALUE(head, term_is_integer); - avm_int_t c = term_to_int(head); - if (UNLIKELY((c < '0') || (c > '9'))) { + avm_int_t digit = to_digit_index(c); + if(UNLIKELY(digit == -1 || digit >= base)) { RAISE_ERROR(BADARG_ATOM); } //TODO: fix this - if (acc > INT64_MAX / 10) { + if (acc > INT64_MAX / base) { // overflow error is not standard, but we need it since we are running on an embedded device RAISE_ERROR(OVERFLOW_ATOM); } - acc = (acc * 10) + (c - '0'); + acc = (acc * base) + digit; digits++; t = term_get_list_tail(t); if (!term_is_list(t)) { diff --git a/src/libAtomVM/nifs.gperf b/src/libAtomVM/nifs.gperf index 7bc97a0c8..3131a20cc 100644 --- a/src/libAtomVM/nifs.gperf +++ b/src/libAtomVM/nifs.gperf @@ -72,6 +72,7 @@ erlang:integer_to_list/2, &integer_to_list_nif erlang:link/1, &link_nif erlang:list_to_binary/1, &list_to_binary_nif erlang:list_to_integer/1, &list_to_integer_nif +erlang:list_to_integer/2, &list_to_integer_nif erlang:list_to_float/1, &list_to_float_nif erlang:list_to_tuple/1, &list_to_tuple_nif erlang:iolist_size/1, &iolist_size_nif diff --git a/tests/erlang_tests/test_list_to_integer.erl b/tests/erlang_tests/test_list_to_integer.erl index d01a17999..e37851d05 100644 --- a/tests/erlang_tests/test_list_to_integer.erl +++ b/tests/erlang_tests/test_list_to_integer.erl @@ -23,10 +23,16 @@ -export([start/0, sum_integers/2, append_0/1]). start() -> - sum_integers(append_0("10"), "-1") + safe_list_to_integer("--") - safe_list_to_integer(nan) + - safe_list_to_integer("+10") - 10 + safe_list_to_integer("-") - 5 + safe_list_to_integer("+") - - 5 + - safe_list_to_integer("") - 5. + sum_integers(append_0("10"), "-1") + + safe_list_to_integer("--") - 5 + + safe_list_to_integer(nan) - 5 + + safe_list_to_integer("+10") - 10 + + safe_list_to_integer("-") - 5 + + safe_list_to_integer("+") - 5 + + safe_list_to_integer("") - 5 + + safe_list_to_integer("0a", 16) - 10 + + safe_list_to_integer("-0a", 16) + 10 + + safe_list_to_integer("1010", 2) - 10. append_0(L) -> L ++ "0". @@ -35,7 +41,9 @@ sum_integers(A, B) -> list_to_integer(A) + list_to_integer(B). safe_list_to_integer(A) -> - try list_to_integer(A) of + safe_list_to_integer(A, 10). +safe_list_to_integer(A, Base) -> + try list_to_integer(A, Base) of AnyValue -> AnyValue catch error:badarg ->