blob: 73d50c6b97e4ddac803c230e9b8a87b832730f96 [file] [log] [blame]
%%%============================================================================
%%% Licensed under the Apache License, Version 2.0 (the "License");
%%% you may not use this file except in compliance with the License.
%%% You may obtain a copy of the License at
%%%
%%% http://www.apache.org/licenses/LICENSE-2.0
%%%
%%% Unless required by applicable law or agreed to in writing, software
%%% distributed under the License is distributed on an "AS IS" BASIS,
%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%%% See the License for the specific language governing permissions and
%%% limitations under the License.
%%%============================================================================
%%% @private
-module(meck_args_matcher).
-export_type([args_spec/0]).
-export_type([opt_args_spec/0]).
-export_type([args_matcher/0]).
%% API
-export([new/1]).
-export([arity/1]).
-export([match/2]).
%%%============================================================================
%%% Definitions
%%%============================================================================
-record(args_matcher, {opt_args_pattern :: opt_args_pattern(),
comp_match_spec :: ets:comp_match_spec(),
has_matchers :: boolean()}).
%%%============================================================================
%%% Types
%%%============================================================================
-type opt_args_spec() :: args_spec() | '_'.
-type args_spec() :: args_pattern() | non_neg_integer().
-type opt_args_pattern() :: args_pattern() | '_'.
-type args_pattern() :: [any() | '_' | meck_matcher:matcher()].
-opaque args_matcher() :: #args_matcher{}.
%%%============================================================================
%%% API
%%%============================================================================
-spec new(opt_args_spec()) -> args_matcher().
new('_') ->
MatchSpecItem = meck_util:match_spec_item({'_'}),
CompMatchSpec = ets:match_spec_compile([MatchSpecItem]),
#args_matcher{opt_args_pattern = '_',
comp_match_spec = CompMatchSpec,
has_matchers = false};
new(Arity) when is_number(Arity) ->
ArgsPattern = lists:duplicate(Arity, '_'),
MatchSpecItem = meck_util:match_spec_item({ArgsPattern}),
CompMatchSpec = ets:match_spec_compile([MatchSpecItem]),
#args_matcher{opt_args_pattern = ArgsPattern,
comp_match_spec = CompMatchSpec,
has_matchers = false};
new(ArgsPattern) when is_list(ArgsPattern) ->
{HasMatchers, Pattern} = case strip_off_matchers(ArgsPattern) of
unchanged ->
{false, ArgsPattern};
StrippedArgsSpec ->
{true, StrippedArgsSpec}
end,
MatchSpecItem = meck_util:match_spec_item({Pattern}),
CompMatchSpec = ets:match_spec_compile([MatchSpecItem]),
#args_matcher{opt_args_pattern = ArgsPattern,
comp_match_spec = CompMatchSpec,
has_matchers = HasMatchers}.
-spec arity(args_matcher()) -> Arity::non_neg_integer().
arity(#args_matcher{opt_args_pattern = ArgsPattern}) ->
erlang:length(ArgsPattern).
-spec match(Args::any(), args_matcher()) -> boolean().
match(Args, #args_matcher{opt_args_pattern = OptArgsPattern,
comp_match_spec = CompMatchSpec,
has_matchers = HasMatchers}) ->
case ets:match_spec_run([{Args}], CompMatchSpec) of
[] ->
false;
_Matches when HasMatchers andalso erlang:is_list(OptArgsPattern) ->
check_by_matchers(Args, OptArgsPattern);
_Matches ->
true
end.
%%%============================================================================
%%% Internal functions
%%%============================================================================
-spec strip_off_matchers(args_pattern()) ->
NewArgsPattern::args_pattern() | unchanged.
strip_off_matchers(ArgsPattern) ->
strip_off_matchers(ArgsPattern, [], false).
-spec strip_off_matchers(args_pattern(), Stripped::[any() | '_'], boolean()) ->
NewArgsPattern::args_pattern() | unchanged.
strip_off_matchers([ArgPattern | Rest], Stripped, HasMatchers) ->
case meck_matcher:is_matcher(ArgPattern) of
true ->
strip_off_matchers(Rest, ['_' | Stripped], true);
_ ->
strip_off_matchers(Rest, [ArgPattern | Stripped], HasMatchers)
end;
strip_off_matchers([], Stripped, true) ->
lists:reverse(Stripped);
strip_off_matchers([], _Stripped, false) ->
unchanged.
-spec check_by_matchers(Args ::[any()], MaybeMatchers::[any()]) -> boolean().
check_by_matchers([Arg | RestArgs], [MaybeMatcher | RestMaybeMatchers]) ->
case meck_matcher:match_ignore(Arg, MaybeMatcher) of
true ->
check_by_matchers(RestArgs, RestMaybeMatchers);
_Else ->
false
end;
check_by_matchers([], []) ->
true.