_site/cover/scran_sequence.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 operate on sequences of input.
17
18 -module(scran_sequence).
19
20
21 -feature(maybe_expr, enable).
22
23
24 -export([combined_with/2]).
25 -export([combined_with/3]).
26 -export([delimited/3]).
27 -export([followed_with/2]).
28 -export([pair/2]).
29 -export([preceded/2]).
30 -export([separated_pair/3]).
31 -export([sequence/1]).
32 -export([terminated/2]).
33 -export([zip/2]).
34 -include_lib("kernel/include/logger.hrl").
35
36
37 %% @doc Matches an object from the first parser and discards it, then
38 %% gets an object from the second parser, and finally matches an
39 %% object from the third parser and discards it.
40 %%
41
42 -spec delimited(scran:parser(),
43 scran:parser(),
44 scran:parser()) -> scran:parser().
45
46 delimited(First, Second, Third) ->
47 5 fun
48 (Input) ->
49 5 ?LOG_DEBUG(#{first => scran_debug:pp(First),
50 second => scran_debug:pp(Second),
51 third => scran_debug:pp(Third),
52 5 input => Input}),
53 5 maybe
54 5 {SecondInput, _} ?= First(Input),
55 3 {ThirdInput, Result} ?= Second(SecondInput),
56 3 {Remaining, _} ?= Third(ThirdInput),
57 3 ?LOG_DEBUG(#{remaining => Remaining, result => Result}),
58 3 {Remaining, Result}
59 end
60 end.
61
62
63 %% @doc Gets an object from the first parser, then gets another object
64 %% from the second parser.
65 %%
66
67 -spec pair(scran:parser(), scran:parser()) -> scran:parser().
68
69 pair(First, Second) ->
70 5 fun
71 (Input) ->
72 5 ?LOG_DEBUG(#{first => scran_debug:pp(First),
73 second => scran_debug:pp(Second),
74 5 input => Input}),
75 5 maybe
76 5 {SecondInput, FirstResult} ?= First(Input),
77 3 {Remaining, SecondResult} ?= Second(SecondInput),
78 3 ?LOG_DEBUG(#{remaining => Remaining,
79 3 result => [FirstResult, SecondResult]}),
80 3 {Remaining, [FirstResult, SecondResult]}
81 end
82 end.
83
84
85 %% @doc Matches an object from the first parser and discards it, then
86 %% gets an object from the second parser.
87 %%
88
89 -spec preceded(scran:parser(), scran:parser()) -> scran:parser().
90
91 preceded(First, Second) ->
92 9 fun
93 (Input) ->
94 9 ?LOG_DEBUG(#{first => scran_debug:pp(First),
95 second => scran_debug:pp(Second),
96 9 input => Input}),
97 9 maybe
98 9 {SecondInput, _} ?= First(Input),
99 5 {Remaining, Result} ?= Second(SecondInput),
100 5 ?LOG_DEBUG(#{remaining => Remaining,
101 5 result => Result}),
102 5 {Remaining, Result}
103 end
104 end.
105
106
107 %% @doc Gets an object from the first parser, then matches an object
108 %% from the separator and discards it, then gets another object from
109 %% the second parser.
110 %%
111
112 -spec separated_pair(scran:parser(),
113 scran:parser(),
114 scran:parser()) -> scran:parser().
115
116 separated_pair(First, Separator, Second) ->
117 19 fun
118 (Input) ->
119 38 ?LOG_DEBUG(#{first => scran_debug:pp(First),
120 separator => scran_debug:pp(Separator),
121 second => scran_debug:pp(Second),
122 38 input => Input}),
123 38 maybe
124 38 {SeparatorInput, FirstResult} ?= First(Input),
125 28 {SecondInput, _} ?= Separator(SeparatorInput),
126 24 {Remaining, SecondResult} ?= Second(SecondInput),
127 22 ?LOG_DEBUG(#{remaining => Remaining,
128 22 result => [FirstResult, SecondResult]}),
129 22 {Remaining, [FirstResult, SecondResult]}
130 end
131 end.
132
133
134 %% @doc Gets an object from the first parser, then matches an object
135 %% from the second parser and discards it.
136 %%
137
138 -spec terminated(scran:parser(), scran:parser(I, O)) -> scran:parser(I, O).
139
140 terminated(First, Second) ->
141 5 fun
142 (Input) ->
143 5 ?LOG_DEBUG(#{first => scran_debug:pp(First),
144 second => scran_debug:pp(Second),
145 5 input => Input}),
146 5 maybe
147 5 {SecondInput, FirstResult} ?= First(Input),
148 3 {Remaining, Discarded} ?= Second(SecondInput),
149 3 ?LOG_DEBUG(#{remaining => Remaining,
150 discarded => Discarded,
151 3 result => FirstResult}),
152 3 {Remaining, FirstResult}
153 end
154 end.
155
156
157 %% @doc The input is applied to each step in the sequence.
158
159 %% -spec sequence([scran:parser()]) -> scran:parser().
160
161 sequence(Steps) ->
162 35 fun
163 (Input) ->
164 34 ?FUNCTION_NAME(Steps, {Input, []})
165 end.
166
167
168 -spec sequence([scran:parser()],
169 {scran:input(), [scran:result()]}) -> scran:result().
170
171 sequence([Step | Steps], {Input, Results}) ->
172 63 case Step(Input) of
173 {Remaining, none = Result} ->
174 1 ?LOG_DEBUG(#{step => scran_debug:pp(Step),
175 input => Input,
176 result => Result,
177 results => Results,
178 1 remaining => Remaining}),
179 1 ?FUNCTION_NAME(Steps, {Remaining, Results});
180
181 {Remaining, Result} when is_binary(Input), is_list(Result) ->
182 1 ?LOG_DEBUG(#{step => scran_debug:pp(Step),
183 input => Input,
184 result => Result,
185 results => Results,
186 1 remaining => Remaining}),
187 1 ?FUNCTION_NAME(
188 Steps,
189 {Remaining, lists:reverse(Result) ++ Results});
190
191 {Remaining, Result} when is_list(Result) ->
192 17 ?LOG_DEBUG(#{step => scran_debug:pp(Step),
193 input => Input,
194 result => Result,
195 results => Results,
196 17 remaining => Remaining}),
197 17 ?FUNCTION_NAME(
198 Steps,
199 {Remaining,
200 case io_lib:printable_list(Result) of
201 true ->
202 16 [Result | Results];
203
204 false ->
205 1 lists:reverse(Result) ++ Results
206 end});
207
208 {Remaining, Result} ->
209 30 ?LOG_DEBUG(#{step => scran_debug:pp(Step),
210 result => Result,
211 input => Input,
212 results => Results,
213 30 remaining => Remaining}),
214 30 ?FUNCTION_NAME(
215 Steps,
216 {Remaining, [Result | Results]});
217
218 nomatch ->
219 14 ?LOG_DEBUG(#{input => Input,
220 14 nomatch => scran_debug:pp(Step)}),
221 14 nomatch
222 end;
223
224 sequence([], {Remainder, Results}) ->
225 20 ?LOG_DEBUG(#{remainder => Remainder, results => Results}),
226 20 {Remainder, lists:reverse(Results)}.
227
228
229 %% @doc Zip the result of the first parser with the result of second.
230
231 -spec zip(scran:parser(I, K), scran:parser(I, V)) -> scran:parser(I, [{K, V}]).
232
233 zip(First, Second) ->
234 2 fun
235 (Input) ->
236 2 ?LOG_DEBUG(#{first => scran_debug:pp(First),
237 second => scran_debug:pp(Second),
238 2 input => Input}),
239
240 2 maybe
241 2 {SecondInput, Keys} ?= (First)(Input),
242 2 {Remaining, Values} ?= (Second)(SecondInput),
243
244 2 ?LOG_DEBUG(#{keys => Keys,
245 remaining => Remaining,
246 2 values => Values}),
247
248 2 try
249 2 {Remaining, lists:zip(Keys, Values)}
250
251 catch
252 error:function_clause ->
253 1 nomatch
254 end
255 end
256 end.
257
258
259 %% doc Using the result of the first parser to initialise the second parser.
260
261 -spec followed_with(scran:parser(), scran:with_result()) -> scran:parser().
262
263 followed_with(First, Second) ->
264 1 fun
265 (Input) ->
266 6 ?LOG_DEBUG(#{first => scran_debug:pp(First),
267 second => scran_debug:pp(Second),
268 6 input => Input}),
269
270 6 maybe
271 6 {SecondInput, FirstResult} ?= First(Input),
272 5 (Second(FirstResult))(SecondInput)
273 end
274 end.
275
276 %% @doc Combine the result from second parser initialised with the
277 %% result of the first parser.
278 %%
279 %% -spec combined_with(scran:parser(), scran:with_result(), scran:combiner()) -> scran:parser().
280
281 combined_with(First, Second, Combiner) ->
282 1 fun
283 (Input) ->
284 6 ?LOG_DEBUG(#{first => scran_debug:pp(First),
285 second => scran_debug:pp(Second),
286 combiner => scran_debug:pp(Combiner),
287 6 input => Input}),
288
289 6 maybe
290 6 {SecondInput, FirstResult} ?= First(Input),
291 5 {Remaining, SecondResult} ?= (Second(FirstResult))(SecondInput),
292 2 {Remaining, Combiner(FirstResult, SecondResult)}
293 end
294 end.
295
296
297 %% @doc Combine using maps:merge/2 the result from second parser
298 %% initialised with the result of the first parser.
299 %%
300 %% -spec combined_with(scran:parser(), scran:with_result()) -> scran:parser().
301
302 combined_with(First, Second) ->
303 1 ?FUNCTION_NAME(First, Second, fun maps:merge/2).
Line Hits Source