| %% -*- 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) -> []. |