_site/cover/scran_bytes.COVER.html

1 %% Copyright (c) 2023 Peter Morgan <peter.james.morgan@gmail.com>
2 %%
3 %% Licensed under the Apache License, Version 2.0 (the "License");
4 %% you may not use this file except in compliance with the License.
5 %% You may obtain a copy of the License at
6 %%
7 %% http://www.apache.org/licenses/LICENSE-2.0
8 %%
9 %% Unless required by applicable law or agreed to in writing, software
10 %% distributed under the License is distributed on an "AS IS" BASIS,
11 %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 %% See the License for the specific language governing permissions and
13 %% limitations under the License.
14
15
16 %% @doc Parser combinators that deal with byte inputs
17
18
19 -module(scran_bytes).
20
21
22 -feature(maybe_expr, enable).
23
24
25 -export([bitfield/1]).
26 -export([bitfield/2]).
27 -export([length_encoded/1]).
28 -export([null_terminated/0]).
29 -export([part/2]).
30 -export([split/1]).
31 -export([tag/1]).
32 -export([take/1]).
33 -import(scran_bits, [into_boolean/0]).
34 -import(scran_combinator, [map_parser/2]).
35 -import(scran_combinator, [map_result/2]).
36 -import(scran_multi, [many1/1]).
37 -import(scran_result, [into_map/1]).
38 -include_lib("kernel/include/logger.hrl").
39
40
41 %% @doc Take N bytes from the input, returning the remaining input and
42 %% the taken bytes.
43
44 -spec take(non_neg_integer() | scran:parser(binary(), pos_integer())) -> scran:parser(binary(), binary()).
45
46 take(NumBytesParser) when is_function(NumBytesParser) ->
47 7 fun
48 (Input) ->
49 7 ?LOG_DEBUG(#{num_bytes_parser => scran_debug:pp(NumBytesParser),
50 7 input => Input}),
51
52 7 maybe
53 7 {Remaining, N} ?= NumBytesParser(Input),
54 6 ?LOG_DEBUG(#{remaining => Remaining, n => N}),
55 6 (?FUNCTION_NAME(N))(Remaining)
56 end
57 end;
58
59 take(N) when is_integer(N), N >= 0 ->
60 13 ?LOG_DEBUG(#{n => N}),
61 13 fun
62 (<<Taken:N/bytes, Remaining/bytes>>) ->
63 9 ?LOG_DEBUG(#{n => N,
64 taken => Taken,
65 9 remaining => Remaining}),
66
67 9 {Remaining, Taken};
68
69 (L) when is_list(L), length(L) >= N ->
70 3 {Taken, Remaining} = lists:split(N, L),
71 3 ?LOG_DEBUG(#{l => L, taken => Taken, remaining => Remaining}),
72 3 {Remaining, list_to_binary(Taken)};
73
74 (Otherwise) ->
75 3 ?LOG_DEBUG(#{nomatch => Otherwise, n => N}),
76 3 nomatch
77 end.
78
79
80 %% @doc Read bytes from input until reaching a null byte (which is
81 %% discarded), returning the remaining input and the bytes prior to
82 %% the null.
83
84 -spec null_terminated() -> scran:parser().
85
86 null_terminated() ->
87 27 split(<<0>>).
88
89
90 %% @doc Split the input on the supplied binary pattern, returning the
91 %% input before and leaving the remaining input after.
92
93 -spec split(binary()) -> scran:parser().
94
95 split(Pattern) ->
96 27 fun
97 (Input) ->
98 23 maybe
99 23 [Before, After] ?= binary:split(Input, Pattern),
100 18 {After, Before}
101
102 else
103 [_] ->
104 5 nomatch
105 end
106 end.
107
108
109 %% @doc Use the supplied parser to determine the length of the
110 %% returned taken bytes.
111
112 -spec length_encoded(scran_number:uparser()) -> scran:parser().
113
114 length_encoded(LengthParser) ->
115 3 fun
116 (Input) ->
117 3 ?LOG_DEBUG(#{length_parser => scran_debug:pp(LengthParser),
118 3 input => Input}),
119
120 3 maybe
121 3 {Remaining, Length} ?= LengthParser(Input),
122
123 2 ?LOG_DEBUG(#{remaining => Remaining, length => Length}),
124
125 2 (take(Length))(Remaining)
126 end
127 end.
128
129
130 %% @doc Return the matching input.
131
132 -spec tag(binary()) -> scran:parser(binary(), binary()).
133
134 tag(Tag) ->
135 3 fun
136 (<<Matched:(byte_size(Tag))/bytes, Remaining/bytes>>)
137 when Tag == Matched ->
138 2 {Remaining, Tag};
139
140 (_) ->
141 1 nomatch
142 end.
143
144
145 %% @doc Return part of the input from the supplied position and length.
146 -spec part(non_neg_integer(), integer()) -> scran:parser().
147
148 part(Position, Length) ->
149 2 fun
150 (Input) ->
151 2 try
152 2 {<<>>, binary:part(Input, Position, Length)}
153
154 catch
155 error:badarg ->
156 1 nomatch
157 end
158 end.
159
160
161 %% @doc Given a list of names, return a bitfield.
162 -spec bitfield([atom()]) -> scran:parser(scran:input(), #{atom() => binary()}).
163
164 bitfield(Names) when length(Names) rem 8 == 0 ->
165 2 ?FUNCTION_NAME(Names, take(length(Names) div 8)).
166
167
168 bitfield(Names, Parser) when length(Names) rem 8 == 0 ->
169 2 fun
170 (Input) ->
171 4 (into_map(
172 map_result(
173 map_parser(
174 Parser,
175 many1(into_boolean())),
176 fun
177 (Flags) ->
178 4 lists:zip(Names, Flags)
179 end)))(Input)
180 end.
Line Hits Source