blob: 287334dff617ca4d583776a141af1f2306be1597 [file] [log] [blame]
%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
%% ex: ts=4 sw=4 et
%%
%% This file is part of Triq - Trifork QuickCheck
%%
%% Copyright (c) 2011-2013 by Trifork
%% Copyright 2013-2018 Triq authors
%%
%% 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.
-module(triq_expr).
-export([eval/1,
eval/2,
free_vars/1]).
%%-----------------------------------------------------------------------
%% @doc Evaluate `Body'. Occurrences of `{call,M,F,A}'
%% is replaced by the result of calling `erlang:apply(M,F,A)', and
%% occurrences of `{var,Name}' in `Body' are not substituted.
%%
%% This is a plain function, not a compile_transform or anything like that,
%% so nested functions are not traversed in the substitution. However, nested
%% occurrences of `{call,M,F,A}' are substituted as one would think:
%% depth first, left-to-right.
%%
%% @spec eval(Body::any()) -> any()
%% @equiv eval([],Body)
%% @end
%% -----------------------------------------------------------------------
eval(Term) ->
eval([], Term).
%%-----------------------------------------------------------------------
%% @doc Evaluate `Body', replacing occurrences of `{call,M,F,A}' and `{var,N}'.
%% Occurrences of `{call,M,F,A}' is replaced by `erlang:apply(M,F,A)', and
%% `{var,Name}' is replaced by the value with key `Name' in `PropList'.
%%
%% Exceptions happening when calling `erlang:apply/3' are not caught.
%% If `Name' is unbound i.e., `Name' does not appear in `PropList' or if
%% `Name' is not an atom, `{var,Name}' is unchanged.
%%
%% This is a plain function, not a compile_transform or anything like that,
%% so nested functions are not traversed in the substitution. However, nested
%% occurrences of `{call,M,F,A}' are substituted as one would think:
%% depth first, left-to-right.
%%
%% @spec eval(PropList::[{atom(),any()}], Body::any()) -> any()
%% @end
%%-----------------------------------------------------------------------
eval(PropList, [H|T]) ->
[eval(PropList,H) | eval(PropList,T)];
eval(PropList, Tuple) when is_tuple(Tuple) ->
case tuple_to_list(Tuple) of
[call, Mod, Fun, Args] ->
M = eval(PropList, Mod),
F = eval(PropList, Fun),
A = eval(PropList, Args),
erlang:apply(M,F,A);
[var, Name] when is_integer(Name) ->
case proplists:lookup(Name, PropList) of
none -> {var, Name};
{Name, Value} -> Value
end;
List ->
list_to_tuple(eval(PropList,List))
end;
eval(_, Term) ->
Term.
free_vars([H|T]) -> free_vars(H) ++ free_vars(T);
free_vars({var, Name}=Var) when is_integer(Name) -> [Var];
free_vars(Tuple) when is_tuple(Tuple) ->
lists:concat(lists:map(fun free_vars/1, tuple_to_list(Tuple)));
free_vars(_Term) -> [].