Skip to content

Add binary_part/3 to BIFs #1569

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 9, 2025
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
24 changes: 24 additions & 0 deletions src/libAtomVM/bif.c
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,30 @@ term bif_erlang_bit_size_1(Context *ctx, uint32_t fail_label, int live, term arg
return term_from_int32(len);
}

term bif_erlang_binary_part_3(Context *ctx, uint32_t fail_label, int live, term arg1, term arg2, term arg3)
{
VALIDATE_VALUE_BIF(fail_label, arg1, term_is_binary);
VALIDATE_VALUE_BIF(fail_label, arg2, term_is_integer);
VALIDATE_VALUE_BIF(fail_label, arg3, term_is_integer);

avm_int_t pos = term_to_int(arg2);
avm_int_t len = term_to_int(arg3);
BinaryPosLen slice;
if (UNLIKELY(!term_normalize_binary_pos_len(arg1, pos, len, &slice))) {
RAISE_ERROR_BIF(fail_label, BADARG_ATOM);
}

TERM_DEBUG_ASSERT((sizeof(ctx->x) / sizeof(ctx->x[0])) >= MAX_REG + 1);
ctx->x[live] = arg1;
size_t heap_size = term_sub_binary_heap_size(arg1, len);
if (UNLIKELY(memory_ensure_free_with_roots(ctx, heap_size, live + 1, ctx->x, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
RAISE_ERROR_BIF(fail_label, OUT_OF_MEMORY_ATOM);
}
arg1 = ctx->x[live];

return term_maybe_create_sub_binary(arg1, slice.pos, slice.len, &ctx->heap, ctx->global);
}

term bif_erlang_is_atom_1(Context *ctx, uint32_t fail_label, term arg1)
{
UNUSED(ctx);
Expand Down
1 change: 1 addition & 0 deletions src/libAtomVM/bif.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const struct ExportedFunction *bif_registry_get_handler(AtomString module, AtomS
term bif_erlang_self_0(Context *ctx);
term bif_erlang_byte_size_1(Context *ctx, uint32_t fail_label, int live, term arg1);
term bif_erlang_bit_size_1(Context *ctx, uint32_t fail_label, int live, term arg1);
term bif_erlang_binary_part_3(Context *ctx, uint32_t fail_label, int live, term arg1, term arg2, term arg3);
term bif_erlang_length_1(Context *ctx, uint32_t fail_label, int live, term arg1);

term bif_erlang_is_atom_1(Context *ctx, uint32_t fail_label, term arg1);
Expand Down
1 change: 1 addition & 0 deletions src/libAtomVM/bifs.gperf
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ erlang:self/0, {.bif.base.type = BIFFunctionType, .bif.bif0_ptr = bif_erlang_sel
erlang:length/1, {.gcbif.base.type = GCBIFFunctionType, .gcbif.gcbif1_ptr = bif_erlang_length_1}
erlang:byte_size/1, {.gcbif.base.type = GCBIFFunctionType, .gcbif.gcbif1_ptr = bif_erlang_byte_size_1}
erlang:bit_size/1, {.gcbif.base.type = GCBIFFunctionType, .gcbif.gcbif1_ptr = bif_erlang_bit_size_1}
erlang:binary_part/3, {.gcbif.base.type = GCBIFFunctionType, .gcbif.gcbif3_ptr = bif_erlang_binary_part_3}
erlang:get/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erlang_get_1}
erlang:is_atom/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erlang_is_atom_1}
erlang:is_bitstring/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erlang_is_binary_1}
Expand Down
23 changes: 8 additions & 15 deletions src/libAtomVM/nifs.c
Original file line number Diff line number Diff line change
Expand Up @@ -3078,33 +3078,26 @@ static term nif_binary_last_1(Context *ctx, int argc, term argv[])
static term nif_binary_part_3(Context *ctx, int argc, term argv[])
{
UNUSED(argc);

term bin_term = argv[0];
term pattern_term = argv[0];
term pos_term = argv[1];
term len_term = argv[2];

VALIDATE_VALUE(bin_term, term_is_binary);
VALIDATE_VALUE(pattern_term, term_is_binary);
VALIDATE_VALUE(pos_term, term_is_integer);
VALIDATE_VALUE(len_term, term_is_integer);

int bin_size = term_binary_size(bin_term);
avm_int_t pos = term_to_int(pos_term);
avm_int_t len = term_to_int(len_term);

if (len < 0) {
pos += len;
len = -len;
}

if (UNLIKELY((pos < 0) || (pos > bin_size) || (pos + len > bin_size))) {
BinaryPosLen slice;
if (UNLIKELY(!term_normalize_binary_pos_len(pattern_term, pos, len, &slice))) {
RAISE_ERROR(BADARG_ATOM);
}

size_t size = term_sub_binary_heap_size(bin_term, len);
if (UNLIKELY(memory_ensure_free_with_roots(ctx, size, 1, &bin_term, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
size_t heap_size = term_sub_binary_heap_size(pattern_term, len);
if (UNLIKELY(memory_ensure_free_with_roots(ctx, heap_size, 1, &pattern_term, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
}
return term_maybe_create_sub_binary(bin_term, pos, len, &ctx->heap, ctx->global);

return term_maybe_create_sub_binary(pattern_term, slice.pos, slice.len, &ctx->heap, ctx->global);
}

static term nif_binary_split(Context *ctx, int argc, term argv[])
Expand Down
33 changes: 33 additions & 0 deletions src/libAtomVM/term.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@ struct PrinterFun
printer_function_t print;
};

typedef struct BinaryPosLen
{
avm_int_t pos;
avm_int_t len;
} BinaryPosLen;

enum RefcBinaryFlags
{
RefcNoFlags = 0,
Expand Down Expand Up @@ -1130,6 +1136,33 @@ static inline term term_create_empty_binary(size_t size, Heap *heap, GlobalConte
return t;
}

static inline bool term_normalize_binary_pos_len(term binary, avm_int_t pos, avm_int_t len, BinaryPosLen *pos_len)
{
avm_int_t size = (avm_int_t) term_binary_size(binary);
if (len < 0) {
pos += len;
len = -len;
}

if (UNLIKELY((pos < 0) || (pos > size) || (pos + len > size))) {
return false;
}

pos_len->pos = pos;
pos_len->len = len;
return true;
}

static inline bool term_is_invalid_binary_pos_len(BinaryPosLen pos_len)
{
return pos_len.pos == -1 && pos_len.len == -1;
}

static inline BinaryPosLen term_invalid_binary_pos_len(void)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, last minute review: term_is_invalid_binary_pos_len and term_invalid_binary_pos_len are unused, let's remove them.
Also, for the future: it is not necessary having (void) for functions with no parameters. () is ok.

{
return (BinaryPosLen) { .pos = -1, .len = -1 };
}

/**
* @brief Insert an binary into a binary (using bit syntax).
*
Expand Down
91 changes: 46 additions & 45 deletions tests/erlang_tests/test_binary_part.erl
Original file line number Diff line number Diff line change
Expand Up @@ -20,67 +20,68 @@

-module(test_binary_part).

-export([start/0, id/1, some_part/1, first_part/1, empty_part/1, compare_bin/2]).
-export([start/0, id/1, fail_with_badarg/1, compare_bin/2, as_guard/4]).
-define(ID(Arg), ?MODULE:id(Arg)).

start() ->
B1 = some_part(id(<<"012Testxyz">>)),
B2 = first_part(id(<<"First01234">>)),
B3 = some_part(id(<<"XYZLast">>)),
B4 = empty_part(id(<<"">>)),
B5 = first_part(id(<<"01234">>)),
B6 = not_fail2(1, -1),
ok = test_with_binary_part_fun(?ID(fun binary:part/3)),
ok = test_with_binary_part_fun(?ID(fun erlang:binary_part/3)),
ok = test_with_binary_part_fun(
?ID(fun(Bin, Pos, Len) ->
Pattern = binary_part(Bin, Pos, Len),
?MODULE:as_guard(Bin, Pattern, Pos, Len)
end)
),
0.

compare_bin(B1, <<"Test">>) + compare_bin(B2, <<"First">>) + compare_bin(B3, <<"Last">>) +
byte_size(B4) + compare_bin(B5, <<"01234">>) + fail1(<<":(">>) + fail1({0, 1, 2, 3, 4}) +
fail2(-1, 0) + fail2(0, -1) + compare_bin(<<":">>, B6) + fail2(-1, 2) + fail2(1, fail) +
fail2(fail, 1).
test_with_binary_part_fun(BinaryPart) ->
Middle = BinaryPart(?ID(<<"012Testxyz">>), 3, 4),
ok = compare_bin(Middle, <<"Test">>),
First = BinaryPart(?ID(<<"First01234">>), 0, 5),
ok = compare_bin(First, <<"First">>),
Last = BinaryPart(?ID(<<"XYZLast">>), 3, 4),
ok = compare_bin(Last, <<"Last">>),
Empty = BinaryPart(?ID(<<"">>), 0, 0),
0 = byte_size(Empty),
All = BinaryPart(?ID(<<"01234">>), 0, 5),
ok = compare_bin(All, <<"01234">>),
NegativeCount = BinaryPart(?ID(<<"xyz">>), 1, -1),
ok = compare_bin(NegativeCount, <<"x">>),
ok = fail_with_badarg(fun() -> BinaryPart(?ID(<<"PastEnd">>), 0, 8) end),
BadBinary = {0, 1, 2, 3, 4},
ok = fail_with_badarg(fun() -> BinaryPart(?ID(BadBinary), 0, 1) end),
ok = fail_with_badarg(fun() -> BinaryPart(?ID(BadBinary), 0, 0) end),
ok = fail_with_badarg(fun() -> BinaryPart(?ID(<<"PosBeforeStart">>), -1, 0) end),
ok = fail_with_badarg(fun() -> BinaryPart(?ID(<<"LenBeforeStart">>), 0, -1) end),
ok = fail_with_badarg(fun() -> BinaryPart(?ID(<<"PartiallyBeforeStart">>), -1, 2) end),
ok = fail_with_badarg(fun() -> BinaryPart(?ID(<<"BadPos">>), fail, 1) end),
ok = fail_with_badarg(fun() -> BinaryPart(?ID(<<"BadLen">>), 1, fail) end),
ok.

empty_part(Bin1) ->
binary:part(id(Bin1), 0, 0).

first_part(Bin1) ->
binary:part(id(Bin1), 0, 5).

some_part(Bin1) ->
binary:part(id(Bin1), 3, 4).
as_guard(Bin, Pattern, Pos, Len) when binary_part(Bin, Pos, Len) == Pattern ->
Pattern;
as_guard(_Bin, _Pattern, _Pos, _Len) ->
erlang:error(badarg).

id(X) ->
X.

fail1(X) ->
try binary:part(X, 0, 3) of
_Any -> 1000
catch
error:badarg -> 1;
_:_ -> 2000
end.

fail2(X, Y) ->
try binary:part(<<":((">>, X, Y) of
_Any -> 1000
catch
error:badarg -> 1;
_:_ -> 2000
end.

not_fail2(X, Y) ->
try binary:part(<<":((">>, X, Y) of
Any -> Any
fail_with_badarg(Fun) ->
try Fun() of
Ret -> {unexpected, Ret}
catch
error:badarg -> 1;
_:_ -> 2000
error:badarg -> ok;
C:E -> {unexpected, C, E}
end.

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

compare_bin(_Bin1, _Bin2, -1) ->
1;
ok;
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
B1 -> compare_bin(Bin1, Bin2, Index - 1);
_Any -> error
end.
2 changes: 1 addition & 1 deletion tests/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ struct Test tests[] = {
TEST_CASE_EXPECTED(test_atom_to_binary, 1),
TEST_CASE(test_unicode),

TEST_CASE_EXPECTED(test_binary_part, 12),
TEST_CASE(test_binary_part),
TEST_CASE(test_binary_split),

TEST_CASE_COND(plusone, 134217728, LONG_MAX != 9223372036854775807),
Expand Down
Loading