Skip to content

Commit cac7b78

Browse files
committed
Add binary_part/3 to BIFs
Signed-off-by: Jakub Gonet <jakub.gonet@swmansion.com>
1 parent 56bdc67 commit cac7b78

File tree

6 files changed

+104
-63
lines changed

6 files changed

+104
-63
lines changed

src/libAtomVM/bif.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,45 @@ term bif_erlang_bit_size_1(Context *ctx, uint32_t fail_label, int live, term arg
108108
return term_from_int32(len);
109109
}
110110

111+
bool get_sub_binary_slice(term bin_term, term pos_term, term len_term, struct SubBinarySlice *slice)
112+
{
113+
int bin_size = term_binary_size(bin_term);
114+
avm_int_t pos = term_to_int(pos_term);
115+
avm_int_t len = term_to_int(len_term);
116+
117+
if (len < 0) {
118+
pos += len;
119+
len = -len;
120+
}
121+
122+
if (UNLIKELY((pos < 0) || (pos > bin_size) || (pos + len > bin_size))) {
123+
return false;
124+
}
125+
126+
slice->pos = pos;
127+
slice->len = len;
128+
slice->heap_size = term_sub_binary_heap_size(bin_term, len);
129+
return true;
130+
}
131+
132+
term bif_erlang_binary_part_3(Context *ctx, uint32_t fail_label, int live, term arg1, term arg2, term arg3)
133+
{
134+
VALIDATE_VALUE_BIF(fail_label, arg1, term_is_binary);
135+
VALIDATE_VALUE_BIF(fail_label, arg2, term_is_integer);
136+
VALIDATE_VALUE_BIF(fail_label, arg3, term_is_integer);
137+
138+
struct SubBinarySlice slice;
139+
if (UNLIKELY(!get_sub_binary_slice(arg1, arg2, arg3, &slice))) {
140+
RAISE_ERROR_BIF(fail_label, BADARG_ATOM);
141+
}
142+
143+
if (UNLIKELY(memory_ensure_free_with_roots(ctx, slice.heap_size, live, ctx->x, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
144+
RAISE_ERROR_BIF(fail_label, OUT_OF_MEMORY_ATOM);
145+
}
146+
147+
return term_maybe_create_sub_binary(ctx->x[0], slice.pos, slice.len, &ctx->heap, ctx->global);
148+
}
149+
111150
term bif_erlang_is_atom_1(Context *ctx, uint32_t fail_label, term arg1)
112151
{
113152
UNUSED(ctx);

src/libAtomVM/bif.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,20 @@ extern "C" {
3939

4040
#define MAX_BIF_NAME_LEN 260
4141

42+
struct SubBinarySlice
43+
{
44+
avm_int_t pos;
45+
avm_int_t len;
46+
size_t heap_size;
47+
};
48+
4249
const struct ExportedFunction *bif_registry_get_handler(AtomString module, AtomString function, int arity);
4350

4451
term bif_erlang_self_0(Context *ctx);
4552
term bif_erlang_byte_size_1(Context *ctx, uint32_t fail_label, int live, term arg1);
4653
term bif_erlang_bit_size_1(Context *ctx, uint32_t fail_label, int live, term arg1);
54+
term bif_erlang_binary_part_3(Context *ctx, uint32_t fail_label, int live, term arg1, term arg2, term arg3);
55+
bool get_sub_binary_slice(term bin_term, term pos_term, term len_term, struct SubBinarySlice *slice);
4756
term bif_erlang_length_1(Context *ctx, uint32_t fail_label, int live, term arg1);
4857

4958
term bif_erlang_is_atom_1(Context *ctx, uint32_t fail_label, term arg1);

src/libAtomVM/bifs.gperf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ erlang:self/0, {.bif.base.type = BIFFunctionType, .bif.bif0_ptr = bif_erlang_sel
3939
erlang:length/1, {.gcbif.base.type = GCBIFFunctionType, .gcbif.gcbif1_ptr = bif_erlang_length_1}
4040
erlang:byte_size/1, {.gcbif.base.type = GCBIFFunctionType, .gcbif.gcbif1_ptr = bif_erlang_byte_size_1}
4141
erlang:bit_size/1, {.gcbif.base.type = GCBIFFunctionType, .gcbif.gcbif1_ptr = bif_erlang_bit_size_1}
42+
erlang:binary_part/3, {.gcbif.base.type = GCBIFFunctionType, .gcbif.gcbif3_ptr = bif_erlang_binary_part_3}
4243
erlang:get/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erlang_get_1}
4344
erlang:is_atom/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erlang_is_atom_1}
4445
erlang:is_bitstring/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erlang_is_binary_1}

src/libAtomVM/nifs.c

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3078,33 +3078,24 @@ static term nif_binary_last_1(Context *ctx, int argc, term argv[])
30783078
static term nif_binary_part_3(Context *ctx, int argc, term argv[])
30793079
{
30803080
UNUSED(argc);
3081-
3082-
term bin_term = argv[0];
3081+
term pattern_term = argv[0];
30833082
term pos_term = argv[1];
30843083
term len_term = argv[2];
3085-
3086-
VALIDATE_VALUE(bin_term, term_is_binary);
3084+
VALIDATE_VALUE(pattern_term, term_is_binary);
30873085
VALIDATE_VALUE(pos_term, term_is_integer);
30883086
VALIDATE_VALUE(len_term, term_is_integer);
30893087

3090-
int bin_size = term_binary_size(bin_term);
3091-
avm_int_t pos = term_to_int(pos_term);
3092-
avm_int_t len = term_to_int(len_term);
3093-
3094-
if (len < 0) {
3095-
pos += len;
3096-
len = -len;
3097-
}
3098-
3099-
if (UNLIKELY((pos < 0) || (pos > bin_size) || (pos + len > bin_size))) {
3088+
struct SubBinarySlice slice;
3089+
if (!get_sub_binary_slice(pattern_term, pos_term, len_term, &slice)) {
31003090
RAISE_ERROR(BADARG_ATOM);
31013091
}
31023092

3103-
size_t size = term_sub_binary_heap_size(bin_term, len);
3104-
if (UNLIKELY(memory_ensure_free_with_roots(ctx, size, 1, &bin_term, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
3093+
// we need to preserve only binary
3094+
if (UNLIKELY(memory_ensure_free_with_roots(ctx, slice.heap_size, 1, &pattern_term, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
31053095
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
31063096
}
3107-
return term_maybe_create_sub_binary(bin_term, pos, len, &ctx->heap, ctx->global);
3097+
3098+
return term_maybe_create_sub_binary(pattern_term, slice.pos, slice.len, &ctx->heap, ctx->global);
31083099
}
31093100

31103101
static term nif_binary_split(Context *ctx, int argc, term argv[])

tests/erlang_tests/test_binary_part.erl

Lines changed: 46 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -20,67 +20,68 @@
2020

2121
-module(test_binary_part).
2222

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

2526
start() ->
26-
B1 = some_part(id(<<"012Testxyz">>)),
27-
B2 = first_part(id(<<"First01234">>)),
28-
B3 = some_part(id(<<"XYZLast">>)),
29-
B4 = empty_part(id(<<"">>)),
30-
B5 = first_part(id(<<"01234">>)),
31-
B6 = not_fail2(1, -1),
27+
ok = test_with_binary_part_fun(?ID(fun binary:part/3)),
28+
ok = test_with_binary_part_fun(?ID(fun erlang:binary_part/3)),
29+
ok = test_with_binary_part_fun(
30+
?ID(fun(Bin, Pos, Len) ->
31+
Pattern = binary_part(Bin, Pos, Len),
32+
?MODULE:as_guard(Bin, Pattern, Pos, Len)
33+
end)
34+
),
35+
0.
3236

33-
compare_bin(B1, <<"Test">>) + compare_bin(B2, <<"First">>) + compare_bin(B3, <<"Last">>) +
34-
byte_size(B4) + compare_bin(B5, <<"01234">>) + fail1(<<":(">>) + fail1({0, 1, 2, 3, 4}) +
35-
fail2(-1, 0) + fail2(0, -1) + compare_bin(<<":">>, B6) + fail2(-1, 2) + fail2(1, fail) +
36-
fail2(fail, 1).
37+
test_with_binary_part_fun(BinaryPart) ->
38+
Middle = BinaryPart(?ID(<<"012Testxyz">>), 3, 4),
39+
ok = compare_bin(Middle, <<"Test">>),
40+
First = BinaryPart(?ID(<<"First01234">>), 0, 5),
41+
ok = compare_bin(First, <<"First">>),
42+
Last = BinaryPart(?ID(<<"XYZLast">>), 3, 4),
43+
ok = compare_bin(Last, <<"Last">>),
44+
Empty = BinaryPart(?ID(<<"">>), 0, 0),
45+
0 = byte_size(Empty),
46+
All = BinaryPart(?ID(<<"01234">>), 0, 5),
47+
ok = compare_bin(All, <<"01234">>),
48+
NegativeCount = BinaryPart(?ID(<<"xyz">>), 1, -1),
49+
ok = compare_bin(NegativeCount, <<"x">>),
50+
ok = fail_with_badarg(fun() -> BinaryPart(?ID(<<"PastEnd">>), 0, 8) end),
51+
BadBinary = {0, 1, 2, 3, 4},
52+
ok = fail_with_badarg(fun() -> BinaryPart(?ID(BadBinary), 0, 1) end),
53+
ok = fail_with_badarg(fun() -> BinaryPart(?ID(BadBinary), 0, 0) end),
54+
ok = fail_with_badarg(fun() -> BinaryPart(?ID(<<"PosBeforeStart">>), -1, 0) end),
55+
ok = fail_with_badarg(fun() -> BinaryPart(?ID(<<"LenBeforeStart">>), 0, -1) end),
56+
ok = fail_with_badarg(fun() -> BinaryPart(?ID(<<"PartiallyBeforeStart">>), -1, 2) end),
57+
ok = fail_with_badarg(fun() -> BinaryPart(?ID(<<"BadPos">>), fail, 1) end),
58+
ok = fail_with_badarg(fun() -> BinaryPart(?ID(<<"BadLen">>), 1, fail) end),
59+
ok.
3760

38-
empty_part(Bin1) ->
39-
binary:part(id(Bin1), 0, 0).
40-
41-
first_part(Bin1) ->
42-
binary:part(id(Bin1), 0, 5).
43-
44-
some_part(Bin1) ->
45-
binary:part(id(Bin1), 3, 4).
61+
as_guard(Bin, Pattern, Pos, Len) when binary_part(Bin, Pos, Len) == Pattern ->
62+
Pattern;
63+
as_guard(_Bin, _Pattern, _Pos, _Len) ->
64+
erlang:error(badarg).
4665

4766
id(X) ->
4867
X.
4968

50-
fail1(X) ->
51-
try binary:part(X, 0, 3) of
52-
_Any -> 1000
53-
catch
54-
error:badarg -> 1;
55-
_:_ -> 2000
56-
end.
57-
58-
fail2(X, Y) ->
59-
try binary:part(<<":((">>, X, Y) of
60-
_Any -> 1000
61-
catch
62-
error:badarg -> 1;
63-
_:_ -> 2000
64-
end.
65-
66-
not_fail2(X, Y) ->
67-
try binary:part(<<":((">>, X, Y) of
68-
Any -> Any
69+
fail_with_badarg(Fun) ->
70+
try Fun() of
71+
Ret -> {unexpected, Ret}
6972
catch
70-
error:badarg -> 1;
71-
_:_ -> 2000
73+
error:badarg -> ok;
74+
C:E -> {unexpected, C, E}
7275
end.
7376

7477
compare_bin(Bin1, Bin2) ->
7578
compare_bin(Bin1, Bin2, byte_size(Bin1) - 1).
7679

7780
compare_bin(_Bin1, _Bin2, -1) ->
78-
1;
81+
ok;
7982
compare_bin(Bin1, Bin2, Index) ->
8083
B1 = binary:at(Bin1, Index),
8184
case binary:at(Bin2, Index) of
82-
B1 ->
83-
compare_bin(Bin1, Bin2, Index - 1);
84-
_Any ->
85-
0
85+
B1 -> compare_bin(Bin1, Bin2, Index - 1);
86+
_Any -> error
8687
end.

tests/test.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ struct Test tests[] = {
320320
TEST_CASE_EXPECTED(test_atom_to_binary, 1),
321321
TEST_CASE(test_unicode),
322322

323-
TEST_CASE_EXPECTED(test_binary_part, 12),
323+
TEST_CASE(test_binary_part),
324324
TEST_CASE(test_binary_split),
325325

326326
TEST_CASE_COND(plusone, 134217728, LONG_MAX != 9223372036854775807),

0 commit comments

Comments
 (0)