├── .bashrc ├── .env ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── rebar.config ├── src ├── forms.app.src ├── forms.erl └── forms_pt.erl └── test ├── dummy_module.erl ├── dummy_module2.erl ├── dummy_transform.erl ├── forms_pt_tests.erl ├── forms_test.erl └── no_debug_info.erl /.bashrc: -------------------------------------------------------------------------------- 1 | source .env 2 | 3 | function erl() { 4 | docker run --rm -it -v $(pwd):$(pwd) -w $(pwd) ${ERLANG_IMAGE} erl "$@" 5 | } 6 | 7 | function rebar3() { 8 | docker run --rm -it -v $(pwd):$(pwd) -w $(pwd) ${ERLANG_IMAGE} rebar3 "$@" 9 | } 10 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | ERLANG_VERSION=25 2 | ERLANG_IMAGE=erlang:${ERLANG_VERSION} -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: push 2 | 3 | jobs: 4 | test: 5 | runs-on: ubuntu-18.04 6 | name: OTP ${{matrix.version.otp}} 7 | strategy: 8 | matrix: 9 | version: 10 | - {otp: 17.5, rebar: 3.1.0} 11 | - {otp: 18.3, rebar: 3.1.0} 12 | - {otp: 19.3, rebar: 3.1.0} 13 | - {otp: 20.3, rebar: 3.1.0} 14 | - {otp: 21.3, rebar: 3.1.0} 15 | - {otp: 22.3, rebar: 3.1.0} 16 | - {otp: 23.3, rebar: 3.1.0} 17 | - {otp: 24.3, rebar: 3.20.0} 18 | - {otp: 25.1, rebar: 3.20.0} 19 | steps: 20 | - uses: actions/checkout@v2.0.0 21 | - uses: erlef/setup-beam@v1.15.0 22 | with: 23 | otp-version: ${{matrix.version.otp}} 24 | rebar3-version: ${{matrix.version.rebar}} 25 | - run: make compile test 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | *~ 3 | ebin 4 | *.beam 5 | logs 6 | *.plt 7 | rebar.lock 8 | _build 9 | doc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2016 Enrique Fernández 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ###======================================================================== 2 | ### File: Makefile 3 | ### 4 | ### 5 | ### Author(s): 6 | ### - Enrique Fernandez 7 | ### 8 | ###-- LICENSE ------------------------------------------------------------- 9 | ### The MIT License (MIT) 10 | ### 11 | ### Copyright (c) 2016, Enrique Fernandez 12 | ### 13 | ### Permission is hereby granted, free of charge, to any person obtaining a 14 | ### copy of this software and associated documentation files (the 15 | ### "Software"), to deal in the Software without restriction, including 16 | ### without limitation the rights to use, copy, modify, merge, publish, 17 | ### distribute, sublicense, and/or sell copies of the Software, and to 18 | ### permit persons to whom the Software is furnished to do so, subject to 19 | ### the following conditions: 20 | ### 21 | ### The above copyright notice and this permission notice shall be included 22 | ### in all copies or substantial portions of the Software. 23 | ### 24 | ### THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 25 | ### OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | ### MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 | ### IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 28 | ### CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 29 | ### TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 30 | ### SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | ###======================================================================== 32 | .PHONY: all compile test eunit clean clean-all 33 | 34 | 35 | ## Macros 36 | ##------------------------------------------------------------------------- 37 | REBAR := $(shell which rebar3) 38 | 39 | 40 | ## Settings 41 | ##------------------------------------------------------------------------- 42 | EUNIT_OPTS ?= 43 | 44 | 45 | ## Targets 46 | ##------------------------------------------------------------------------- 47 | all: compile 48 | 49 | compile: 50 | $(REBAR) compile 51 | 52 | test: eunit 53 | 54 | eunit: 55 | $(REBAR) eunit $(EUNIT_OPTS) 56 | 57 | clean: 58 | $(REBAR) clean 59 | 60 | clean-all: clean 61 | $(REBAR) unlock 62 | rm -rf */*~ 63 | rm -rf logs 64 | rm -rf _build 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | forms 2 | ===== 3 | [![Build Status](https://github.com/efcasado/forms/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/efcasado/forms/actions) 4 | 5 | A library that simplifies working with [Erlang's abstract format](http://erlang.org/doc/apps/erts/absform.html). 6 | 7 | Documentation is available at [HexDocs](https://hexdocs.pm/forms/). 8 | 9 | If you want to know more about what `forms` could do for you, check the 10 | following projects: 11 | 12 | - [meta](https://github.com/efcasado/meta) - A metaprogramming library for 13 | Erlang 14 | - [behaviours2](https://github.com/efcasado/behaviours2) - Erlang behaviours 15 | on steroids 16 | 17 | 18 | ### Fetching a module's forms 19 | 20 | The `forms` module features a `read/1` function that can be used to fetch a module's abstract syntax tree (AST). 21 | `read/1` works with both Erlang source files (i.e., files with the `.erl` suffix) and Erlang binary files 22 | (i.e., files with the `.beam` suffix). 23 | 24 | The line below would read the forms from Erlang's internal `lists` module. 25 | 26 | ```erl 27 | forms:read(lists). 28 | ``` 29 | 30 | Similarly, the following line would read the forms from a developer-provided `hello_world` source file. 31 | 32 | ```erl 33 | forms:read("src/hello_world.erl"). 34 | ``` 35 | 36 | 37 | > Note that in order to be able to fetch a beam file's AST the Erlang binary file must have been compiled using the 38 | > `debug_info` option (e.g., `erlc -o ebin +debug_info src/hello_world.erl`). Obviously, this is not a requirement 39 | > when reading the forms from a source file. 40 | 41 | 42 | ### High-order functions 43 | 44 | Traversing an Erlang module's AST is now as simple as traversing a list. 45 | The `forms` library ships with the `map/{2,3}`, `reduce\{3,4}`, `mr/3`, `filter/2`, `any/2` and `all/2` functions, 46 | which are anologous to those available in the `lists` module. 47 | 48 | Most of the above mentioned functions support two different traversal modes: 1) (valid) `forms only` and 2) `full`. 49 | The latter (i.e., `full`) is the default traversal mode. If one wants to change to `forms_only`, one can do so by using the 50 | optional `options` argument (e.g., `forms:map(Fun, Forms, _Opts = [forms_only]).`. 51 | 52 | ### Debug functions 53 | 54 | Working with Erlang's abstract format is often tedious because Erlang developers are not used to see Erlang abstract code. 55 | Unlike `Lisp`, Erlang is not `homonoic`, which means that Erlang's code does not have the same structure as its AST. Homoicinity makes metaprogramming easier than in a programming language without this property because code can be treated as data. 56 | 57 | To fill this gap, `forms` features the `eval/1`, `from_abstract/1` and `to_abstract/1` debug functions that may come in handy 58 | when working with Erlang's abstract code. 59 | 60 | ##### eval/1 61 | 62 | Evaluate an Erlang expression (in string form) or abstract form. 63 | 64 | ```erl 65 | forms:eval("1 + 1."). 66 | %% => 2 67 | ``` 68 | 69 | ```erl 70 | forms:eval({op,1,'+',{integer,1,1},{integer,1,1}}). 71 | %% => 2 72 | ``` 73 | 74 | ##### to_abstract/1 75 | 76 | Convert the provided Erlang attribute or expression (in string form) to its abstract format representation. 77 | 78 | ```erl 79 | forms:to_abstract("hello(Name) -> io:format(\"Hello, ~s!~n\", [Name])."). 80 | %% => {function,1,hello,1, 81 | %% [{clause,1, 82 | %% [{var,1,'Name'}], 83 | %% [], 84 | %% [{call,1, 85 | %% {remote,1,{atom,1,io},{atom,1,format}}, 86 | %% [{string,1,"Hello, ~s!~n"}, 87 | %% {cons,1,{var,1,'Name'},{nil,1}}]}]}]} 88 | ``` 89 | 90 | ```erl 91 | forms:to_abstract("-export([hello/1])."). 92 | %% => {attribute,1,export,[{hello,1}]} 93 | ``` 94 | 95 | ##### from_abstract/1 96 | 97 | Convert the provided form into its Erlang attribute or expression counterpart. 98 | 99 | ```erl 100 | forms:from_abstract({attribute,1,export,[{hello,1}]}). 101 | %% => "-export([hello/1])." 102 | ``` 103 | 104 | ```erl 105 | forms:from_abstract({function,1,hello,1, 106 | [{clause,1, 107 | [{var,1,'Name'}], 108 | [], 109 | [{call,1, 110 | {remote,1,{atom,1,io},{atom,1,format}}, 111 | [{string,1,"Hello, ~s!~n"}, 112 | {cons,1,{var,1,'Name'},{nil,1}}]}]}]}). 113 | %% => "hello(Name) -> io:format(\"Hello, ~s!~n\", [Name])." 114 | ``` 115 | 116 | ### Examples 117 | 118 | Count how many times the anonymous variable (i.e., `'_'`) is used in the lists module. 119 | 120 | ```erl 121 | Forms = forms:read(lists), 122 | forms:reduce(fun({var, _, '_'}, Count) -> Count + 1; (_, Count) -> Count end, 0, Forms). 123 | %% => 57 124 | ``` 125 | 126 | Easy. Isn't it? :-) 127 | 128 | 129 | ### Author(s) 130 | 131 | - Enrique Fernandez `` 132 | 133 | 134 | ### License 135 | 136 | > The MIT License (MIT) 137 | > 138 | > Copyright (c) 2014-2018, Enrique Fernandez 139 | > 140 | > Permission is hereby granted, free of charge, to any person obtaining a copy 141 | > of this software and associated documentation files (the "Software"), to deal 142 | > in the Software without restriction, including without limitation the rights 143 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 144 | > copies of the Software, and to permit persons to whom the Software is 145 | > furnished to do so, subject to the following conditions: 146 | > 147 | > The above copyright notice and this permission notice shall be included in 148 | > all copies or substantial portions of the Software. 149 | > 150 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 151 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 152 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 153 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 154 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 155 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 156 | > THE SOFTWARE. 157 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | %% -*- mode: erlang -*- 2 | {plugins, [rebar3_hex]}. 3 | 4 | {eunit_first_files, ["test/dummy_transform.erl"]}. 5 | {eunit_erl_opts, [debug_info]}. 6 | -------------------------------------------------------------------------------- /src/forms.app.src: -------------------------------------------------------------------------------- 1 | %% -*- mode: erlang -*- 2 | {application, forms, 3 | [ 4 | {description, 5 | "Toolbox that simplifies working with Erlang's abstract format"}, 6 | {vsn, "0.0.1"}, 7 | {registered, []}, 8 | {modules, 9 | [ 10 | forms, 11 | forms_pt 12 | ]}, 13 | {applications, 14 | [ 15 | kernel, 16 | stdlib 17 | ]}, 18 | {maintainers, 19 | [ 20 | "Enrique Fernandez" 21 | ]}, 22 | {licenses, ["MIT"]}, 23 | {links, [{"Github", "https://github.com/efcasado/forms"}]}]}. 24 | -------------------------------------------------------------------------------- /src/forms.erl: -------------------------------------------------------------------------------- 1 | %%%======================================================================== 2 | %%% File: forms.erl 3 | %%% 4 | %%% Collection of functions that simplify working with Erlang abstract 5 | %%% forms. 6 | %%% 7 | %%% This work has been inpired by the excellent work done by Ulf Wiger in 8 | %%% parse_trans (https://github.com/uwiger/parse_trans). 9 | %%% 10 | %%% Author: Enrique Fernandez 11 | %%% Date: November, 2014 12 | %%% 13 | %%%-- LICENSE ------------------------------------------------------------- 14 | %%% The MIT License (MIT) 15 | %%% 16 | %%% Copyright (c) 2014-2018 Enrique Fernandez 17 | %%% 18 | %%% Permission is hereby granted, free of charge, to any person obtaining 19 | %%% a copy of this software and associated documentation files (the 20 | %%% "Software"), to deal in the Software without restriction, including 21 | %%% without limitation the rights to use, copy, modify, merge, publish, 22 | %%% distribute, sublicense, and/or sell copies of the Software, 23 | %%% and to permit persons to whom the Software is furnished to do so, 24 | %%% subject to the following conditions: 25 | %%% 26 | %%% The above copyright notice and this permission notice shall be included 27 | %%% in all copies or substantial portions of the Software. 28 | %%% 29 | %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 30 | %%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 31 | %%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 32 | %%% IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 33 | %%% CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 34 | %%% TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 35 | %%% SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 36 | %%%======================================================================== 37 | -module(forms). 38 | 39 | %% API 40 | -export( 41 | [ 42 | read/1, 43 | quote/1, 44 | unquote/1, 45 | map/2, map/3, 46 | reduce/3, reduce/4, 47 | mr/3, 48 | filter/2, 49 | any/2, 50 | all/2, 51 | cons_to_list/1, 52 | list_to_cons/1, 53 | %% Debug functions 54 | eval/1, 55 | from_abstract/1, 56 | to_abstract/1 57 | ]). 58 | 59 | %% ======================================================================== 60 | %% Type definitions 61 | %% ======================================================================== 62 | 63 | -type form() :: erl_parse:abstract_form(). 64 | -type forms() :: list(form()). 65 | -type mapf() :: fun((form()) -> any()). 66 | -type redf() :: fun((form(), any()) -> any()). 67 | -type mrf() :: fun((form(), any()) -> {any(), any()}). 68 | -type predicate() :: fun((form()) -> boolean()). 69 | -type opt() :: 'forms_only'. 70 | -type opts() :: list(opt()). 71 | 72 | %% ======================================================================== 73 | %% Macro definitions 74 | %% ======================================================================== 75 | 76 | %% Options supported by functions such as maps, reduce, etc. 77 | -define(OPTS, 78 | [ 79 | forms_only 80 | ]). 81 | 82 | 83 | %% ======================================================================== 84 | %% API 85 | %% ======================================================================== 86 | 87 | %%------------------------------------------------------------------------- 88 | %% @doc 89 | %% Read the Erlang abstract forms from the specified source file or binary 90 | %% compiled using the -debug_info compile option. 91 | %% @end 92 | %%------------------------------------------------------------------------- 93 | -spec read(atom() | iolist()) -> forms(). 94 | read(Module) when is_atom(Module) -> 95 | case beam_lib:chunks(code:which(Module), [abstract_code]) of 96 | {ok, {Module, [{abstract_code, {raw_abstract_v1, Forms}}]}} -> 97 | Forms; 98 | {ok, {no_debug_info, _}} -> 99 | throw({forms_not_found, Module}); 100 | {error, beam_lib, {file_error, _, enoent}} -> 101 | throw({module_not_found, Module}) 102 | end; 103 | read(File) -> 104 | case epp:parse_file(File, []) of 105 | {ok, Forms} -> 106 | Forms; 107 | {ok, Forms, _Extra} -> 108 | Forms; 109 | {error, enoent} -> 110 | throw({file_not_found, File}) 111 | end. 112 | 113 | %%------------------------------------------------------------------------- 114 | %% @doc 115 | %% Calls the provided fun/1 on all given forms, including nested forms. 116 | %% The original forms are replaced by the resulting Erlang term after 117 | %% applying the provided fun/1 on them. 118 | %% @end 119 | %%------------------------------------------------------------------------- 120 | -spec map(mapf(), forms()) -> forms(). 121 | map(Fun, Forms) -> 122 | map(Fun, Forms, []). 123 | 124 | -spec map(mapf(), forms(), opts()) -> forms(). 125 | map(Fun, Forms, Opts) 126 | when is_list(Opts) -> 127 | Opts1 = parse_opts(Opts), 128 | '_map'(Fun, [], Forms, Opts1); 129 | map(Fun, Forms, Opts) -> 130 | '_map'(Fun, [], Forms, Opts). 131 | 132 | '_map'(_Fun, Acc, [], _Opts) -> 133 | lists:reverse(Acc); 134 | '_map'(Fun, Acc, [F| Fs], Opts) when is_list(F) -> 135 | '_map'(Fun, [map(Fun, F, Opts)| Acc], Fs, Opts); 136 | '_map'(Fun, Acc, [F| Fs], Opts) -> 137 | case forms_only(Opts) of 138 | true -> 139 | case is_form(F) of 140 | true -> 141 | case Fun(F) of 142 | {next, T} -> 143 | '_map'(Fun, [T| Acc], Fs, Opts); 144 | T when is_tuple(T) -> 145 | '_map'(Fun, 146 | [list_to_tuple( 147 | map(Fun, tuple_to_list(T), Opts))| Acc], 148 | Fs, 149 | Opts); 150 | F1 -> 151 | '_map'(Fun, [F1| Acc], Fs, Opts) 152 | end; 153 | false -> 154 | '_map'(Fun, [F| Acc], Fs, Opts) 155 | end; 156 | false -> 157 | case Fun(F) of 158 | {next, T} -> 159 | '_map'(Fun, [T| Acc], Fs, Opts); 160 | T when is_tuple(T) -> 161 | '_map'(Fun, 162 | [list_to_tuple( 163 | map(Fun, tuple_to_list(T), Opts))| Acc], 164 | Fs, 165 | Opts); 166 | F1 -> 167 | '_map'(Fun, [F1| Acc], Fs, Opts) 168 | end 169 | end. 170 | 171 | %%------------------------------------------------------------------------- 172 | %% @doc 173 | %% Calls the provided fun/2 on all given forms, including nested forms. 174 | %% fun/2 must return a new accumulator which is passed to the next call. 175 | %% @end 176 | %%------------------------------------------------------------------------- 177 | -spec reduce(redf(), any(), forms()) -> any(). 178 | reduce(Fun, Acc, Forms) -> 179 | reduce(Fun, Acc, Forms, []). 180 | 181 | -spec reduce(redf(), any(), forms(), opts()) -> any(). 182 | reduce(Fun, Acc, Forms, Opts) -> 183 | Opts1 = parse_opts(Opts), 184 | '_reduce'(Fun, Acc, Forms, Opts1). 185 | 186 | '_reduce'(_Fun, Acc, [], _Opts) -> 187 | Acc; 188 | '_reduce'(Fun, Acc, [F| Fs], Opts) when is_tuple(F) -> 189 | case forms_only(Opts) of 190 | true -> 191 | NewAcc = 192 | case is_form(F) of 193 | true -> 194 | Fun(F, Acc); 195 | false -> 196 | Acc 197 | end, 198 | '_reduce'(Fun, 199 | '_reduce'(Fun, NewAcc, tuple_to_list(F), Opts), 200 | Fs, 201 | Opts); 202 | false -> 203 | '_reduce'(Fun, 204 | '_reduce'(Fun, Fun(F, Acc), tuple_to_list(F), Opts), 205 | Fs, 206 | Opts) 207 | end; 208 | '_reduce'(Fun, Acc, [F| Fs], Opts) when is_list(F) -> 209 | '_reduce'(Fun, '_reduce'(Fun, Acc, F, Opts), Fs, Opts); 210 | '_reduce'(Fun, Acc, [F| Fs], Opts) -> 211 | case forms_only(Opts) of 212 | true -> 213 | case is_form(F) of 214 | true -> 215 | '_reduce'(Fun, Fun(F, Acc), Fs, Opts); 216 | false -> 217 | '_reduce'(Fun, Acc, Fs, Opts) 218 | end; 219 | false -> 220 | '_reduce'(Fun, Fun(F, Acc), Fs, Opts) 221 | end. 222 | 223 | %%------------------------------------------------------------------------- 224 | %% @doc 225 | %% Combines the operations of map/2 and reduce/3 into one pass. 226 | %% @end 227 | %%------------------------------------------------------------------------- 228 | -spec mr(mrf(), any(), forms()) -> {any(), any()}. 229 | mr(Fun, Acc, Fs) -> 230 | mr(Fun, Acc, [], Fs). 231 | 232 | mr(_Fun, Acc1, Acc2, []) -> 233 | {Acc1, lists:reverse(Acc2)}; 234 | mr(Fun, Acc1, Acc2, [F| Fs]) when is_list(F) -> 235 | {NewAcc1, NewAcc2} = mr(Fun, Acc1, F), 236 | mr(Fun, NewAcc1, [NewAcc2| Acc2], Fs); 237 | %% mr(Fun, Acc1, Acc2, [F| Fs]) -> 238 | %% case is_form(F) of 239 | %% true -> 240 | %% case Fun(F, Acc1) of 241 | %% {Acc, {next, T}} -> 242 | %% mr(Fun, Acc, [T| Acc2], Fs); 243 | %% {Acc, T} when is_tuple(T) -> 244 | %% {NewAcc1, NewAcc2} = mr(Fun, Acc, tuple_to_list(T)), 245 | %% mr(Fun, NewAcc1, [list_to_tuple(NewAcc2)| Acc2], Fs); 246 | %% {Acc, F1} -> 247 | %% mr(Fun, Acc, [F1| Acc2], Fs) 248 | %% end; 249 | %% false -> 250 | %% mr(Fun, Acc1, [F| Acc2], Fs) 251 | %% end. 252 | mr(Fun, Acc1, Acc2, [F| Fs]) -> 253 | case Fun(F, Acc1) of 254 | {Acc, {next, T}} -> 255 | mr(Fun, Acc, [T| Acc2], Fs); 256 | {Acc, T} when is_tuple(T) -> 257 | {NewAcc1, NewAcc2} = mr(Fun, Acc, tuple_to_list(T)), 258 | mr(Fun, NewAcc1, [list_to_tuple(NewAcc2)| Acc2], Fs); 259 | {Acc, F1} -> 260 | mr(Fun, Acc, [F1| Acc2], Fs) 261 | end. 262 | 263 | 264 | %%------------------------------------------------------------------------- 265 | %% @doc 266 | %% Filter out all forms not meeting the provided predicate. 267 | %% @end 268 | %%------------------------------------------------------------------------- 269 | -spec filter(predicate(), forms()) -> forms(). 270 | filter(Fun, Forms) -> 271 | lists:reverse(reduce( 272 | fun(Form, Acc) -> 273 | case Fun(Form) of 274 | true -> 275 | [Form| Acc]; 276 | false -> 277 | Acc 278 | end 279 | end, 280 | [], 281 | Forms)). 282 | 283 | %%------------------------------------------------------------------------- 284 | %% @doc 285 | %% Check if there is any form meeting the provided predicate. 286 | %% @end 287 | %%------------------------------------------------------------------------- 288 | -spec any(predicate(), forms()) -> boolean(). 289 | any(_Pred, []) -> 290 | false; 291 | any(Pred, [F| Fs]) when is_tuple(F) -> 292 | Any = case is_form(F) of 293 | true -> 294 | Pred(F); 295 | false -> 296 | false 297 | end, 298 | case Any of 299 | true -> 300 | true; 301 | false -> 302 | case any(Pred, tuple_to_list(F)) of 303 | true -> 304 | true; 305 | false -> 306 | any(Pred, Fs) 307 | end 308 | end; 309 | any(Pred, [F| Fs]) when is_list(F) -> 310 | case any(Pred, F) of 311 | true -> 312 | true; 313 | false -> 314 | any(Pred, Fs) 315 | end; 316 | any(Pred, [_F| Fs]) -> 317 | any(Pred, Fs). 318 | 319 | 320 | %%------------------------------------------------------------------------- 321 | %% @doc 322 | %% Check if all forms meet the provided predicate. 323 | %% @end 324 | %%------------------------------------------------------------------------- 325 | -spec all(predicate(), forms()) -> boolean(). 326 | all(_Pred, []) -> 327 | false; 328 | all(Pred, [F| Fs]) when is_tuple(F) -> 329 | All = case is_form(F) of 330 | true -> 331 | Pred(F); 332 | false -> 333 | true 334 | end, 335 | case All of 336 | false -> 337 | false; 338 | true -> 339 | case all(Pred, tuple_to_list(F)) of 340 | false -> 341 | false; 342 | true -> 343 | all(Pred, Fs) 344 | end 345 | end; 346 | all(Pred, [F| Fs]) when is_list(F) -> 347 | case all(Pred, F) of 348 | false -> 349 | false; 350 | true -> 351 | all(Pred, Fs) 352 | end; 353 | all(Pred, [_F| Fs]) -> 354 | all(Pred, Fs). 355 | 356 | 357 | %%------------------------------------------------------------------------- 358 | %% @doc 359 | %% Quote a form so that it can, for instance, be bound to a variable 360 | %% when manipulating Erlang's abstract code. 361 | %% 362 | %% The following abstract code is not valid code 363 | %% ``` 364 | %% {match, 1, {var, 1, 'A'}, 365 | %% {function, 1, foo, 0, 366 | %% [ 367 | %% {clause, 0, [], [], [{atom, 1, foo}]} 368 | %% ]}} 369 | %% ''' 370 | %% 371 | %% because, in Erlang code, it would be equivalent to 372 | %% 373 | %% ``` 374 | %% A = foo() -> foo. 375 | %% ''' 376 | %% 377 | %% which is, obviously, no valid Erlang code. However, one could quote 378 | %% the right-hand side of the above match operation so that it becomes 379 | %% a valid Erlang expression. 380 | %% 381 | %% One could consider that an expression similar to the one below 382 | %% 383 | %% ``` 384 | %% {match, 1, {var, 1, 'A'}, 385 | %% forms:quote( 386 | %% {function, 1, foo, 0, 387 | %% [ 388 | %% {clause, 0, [], [], [{atom, 1, foo}]} 389 | %% ]})} 390 | %% ''' 391 | %% 392 | %% becomes something like 393 | %% 394 | %% ``` 395 | %% A = <<...>>. 396 | %% ''' 397 | %% 398 | %% @end 399 | %%------------------------------------------------------------------------- 400 | quote(Term) -> 401 | {bin, 0, 402 | [ {bin_element, 0, {integer, 0, X}, default, default} 403 | || <> <= term_to_binary(Term) ]}. 404 | 405 | %%------------------------------------------------------------------------- 406 | %% @doc 407 | %% Inverse of the quote/1 function. Takes a quoted form and returns its 408 | %% original form. 409 | %% @end 410 | %%------------------------------------------------------------------------- 411 | unquote({bin, _, BinElements}) -> 412 | binary_to_term( 413 | lists:foldl(fun({bin_element, _, 414 | {integer, _, X}, 415 | default, 416 | default}, 417 | Acc) -> 418 | <> end, 419 | <<>>, 420 | BinElements)); 421 | unquote(Binary) when is_binary(Binary) -> 422 | binary_to_term(Binary). 423 | 424 | 425 | %% ======================================================================== 426 | %% Debug functions 427 | %% ======================================================================== 428 | 429 | %%------------------------------------------------------------------------- 430 | %% @doc 431 | %% Evaluate the provided String expression or abstract form. 432 | %% @end 433 | %%------------------------------------------------------------------------- 434 | -spec eval(string() | form()) -> term(). 435 | eval(Expr) when is_list(Expr) -> 436 | {ok, A, _} = erl_scan:string(Expr), 437 | {ok, B} = erl_parse:parse_exprs(A), 438 | {value, Value, _} = erl_eval:exprs(B, []), 439 | Value; 440 | eval(Form) -> 441 | eval(lists:append(from_abstract(Form), ".")). 442 | 443 | %%------------------------------------------------------------------------- 444 | %% @doc 445 | %% Turn the provided Erlang attribute or expression into its abstract 446 | %% format representation. 447 | %% @end 448 | %%------------------------------------------------------------------------- 449 | -spec to_abstract(string()) -> form(). 450 | to_abstract(String) -> 451 | {ok, Tokens, _EndLocation} = 452 | erl_scan:string(String), 453 | {ok, AbsForm} = 454 | try 455 | {ok, _} = erl_parse:parse_form(Tokens) 456 | catch 457 | _:_ -> 458 | {ok, _} = erl_parse:parse_exprs(Tokens) 459 | end, 460 | AbsForm. 461 | 462 | %%------------------------------------------------------------------------- 463 | %% @doc 464 | %% Turn the provided abstract form into an Erlang representation. 465 | %% @end 466 | %%------------------------------------------------------------------------- 467 | -spec from_abstract(form()) -> string(). 468 | from_abstract(Forms) when is_list(Forms) -> 469 | erl_prettypr:format(erl_syntax:form_list(Forms)); 470 | from_abstract(Form) -> 471 | erl_prettypr:format(erl_syntax:form_list([Form])). 472 | 473 | %%------------------------------------------------------------------------- 474 | %% @doc 475 | %% Convert a cons (abstract representation of a list) into a list 476 | %% @end 477 | %%------------------------------------------------------------------------- 478 | -spec cons_to_list(form()) -> list(). 479 | cons_to_list(Cons) -> 480 | lists:reverse('_cons_to_list'(Cons, [])). 481 | 482 | '_cons_to_list'({nil, _}, Acc) -> 483 | Acc; 484 | '_cons_to_list'({cons, _, H, Cons}, Acc) -> 485 | '_cons_to_list'(Cons, [H| Acc]). 486 | 487 | %%------------------------------------------------------------------------- 488 | %% @doc 489 | %% Convert a list into a cons (abstract representation of a list) 490 | %% @end 491 | %%------------------------------------------------------------------------- 492 | -spec list_to_cons(list()) -> form(). 493 | list_to_cons([]) -> 494 | {nil, 0}; 495 | list_to_cons([H| Tail]) -> 496 | {cons, 0, H, list_to_cons(Tail)}. 497 | 498 | %% ======================================================================== 499 | %% Local functions 500 | %% ======================================================================== 501 | 502 | parse_opts(Opts) -> 503 | [ Opt || Opt <- Opts, lists:member(Opt, ?OPTS) ]. 504 | 505 | forms_only(Opts) -> 506 | proplists:get_value(forms_only, Opts, false). 507 | 508 | %%------------------------------------------------------------------------- 509 | %% @doc 510 | %% Check if the provided abstract form is valid. 511 | %% @end 512 | %%------------------------------------------------------------------------- 513 | -spec is_form(any()) -> boolean(). 514 | is_form(Form) -> 515 | case catch from_abstract(Form) of 516 | {'EXIT', _} -> 517 | false; 518 | _ -> 519 | true 520 | end. 521 | -------------------------------------------------------------------------------- /src/forms_pt.erl: -------------------------------------------------------------------------------- 1 | %%%======================================================================== 2 | %%% File: forms_pt.erl 3 | %%% 4 | %%% Parse transform allowing developers to programatically define functions 5 | %%% using Erlang syntax (as opposed to abstract code). 6 | %%% 7 | %%% When this parse transform finds a call to the `forms:function/3` 8 | %%% pseudo-function, it applies some transformations and replaces it 9 | %%% by a call to `forms_pt:gen_function/4`. 10 | %%% 11 | %%% The following code 12 | %%% 13 | %%% Name = int_to_text, 14 | %%% Bindings = [ 15 | %%% [{'X', 1}, {'Y', "one"}], 16 | %%% [{'X', 2}, {'Y', "two"}] 17 | %%% ], 18 | %%% forms:function(Name, 19 | %%% [ 20 | %%% {fun(X) -> Y end, Bindings}, 21 | %%% {fun(_) -> {error, invalid_input} end, []} 22 | %%% ]). 23 | %%% 24 | %%% generates the function below 25 | %%% 26 | %%% int_to_text(1) -> "one"; 27 | %%% int_to_text(2) -> "two"; 28 | %%% int_to_text(_) -> {error, invalid_input}. 29 | %%% 30 | %%% 31 | %%% Author: Enrique Fernandez 32 | %%% Date: January, 2015 33 | %%% 34 | %%%-- LICENSE ------------------------------------------------------------- 35 | %%% The MIT License (MIT) 36 | %%% 37 | %%% Copyright (c) 2015 Enrique Fernandez 38 | %%% 39 | %%% Permission is hereby granted, free of charge, to any person obtaining 40 | %%% a copy of this software and associated documentation files (the 41 | %%% "Software"), to deal in the Software without restriction, including 42 | %%% without limitation the rights to use, copy, modify, merge, publish, 43 | %%% distribute, sublicense, and/or sell copies of the Software, 44 | %%% and to permit persons to whom the Software is furnished to do so, 45 | %%% subject to the following conditions: 46 | %%% 47 | %%% The above copyright notice and this permission notice shall be included 48 | %%% in all copies or substantial portions of the Software. 49 | %%% 50 | %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 51 | %%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 52 | %%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 53 | %%% IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 54 | %%% CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 55 | %%% TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 56 | %%% SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 57 | %%%======================================================================== 58 | -module(forms_pt). 59 | 60 | %% Parse transform 61 | -export([parse_transform/2]). 62 | %% Functions used by the parse transform 63 | -export([gen_function/3]). 64 | 65 | 66 | %% ======================================================================== 67 | %% Parse transform 68 | %% ======================================================================== 69 | 70 | parse_transform(Forms, _Options) -> 71 | forms:map( 72 | fun({call, _, 73 | {remote, _, 74 | {atom, _, forms}, 75 | {atom, _, function}}, 76 | [ 77 | Name, 78 | {cons, _, 79 | {tuple, _, 80 | [{'fun', _, 81 | {clauses, 82 | [{clause, _, Args, _Guards, _Body}| _]}}, 83 | _Bindings]}, 84 | _Tail} = Clauses 85 | ]}) -> 86 | Arity = length(Args), 87 | %%AbsArity = to_abstract(AbsArity), 88 | 89 | QuotedClauses = 90 | lists:map(fun({tuple, _, 91 | [{'fun', _, {clauses, Cs}}, Bs]}) -> 92 | {tuple, 0, [forms:quote(Cs), Bs]} 93 | end, 94 | forms:cons_to_list(Clauses)), 95 | 96 | QuotedClauses1 = forms:list_to_cons(QuotedClauses), 97 | 98 | %% We cannot pass the function clauses as is because it 99 | %% would not be a valid abstract code for a function call. 100 | %% This is why we convert the function clauses in a binary 101 | %% that we can pass as an argument to the 102 | %% `forms_pt:gen_function/4` function. 103 | %% QuotedClauses = forms:quote(Clauses), 104 | 105 | %% Replace `forms:function/3` pseudo-call by a call to 106 | %% `forms_pt:gen_function/4`, which generates a function 107 | %% and returns its abstract code. 108 | {call,0,{remote,0,{atom,0,?MODULE},{atom,0,gen_function}}, 109 | [ 110 | Name, 111 | %% to_abstract(length(Args)), 112 | {integer, 0, Arity}, 113 | QuotedClauses1 114 | ]}; 115 | (Other) -> 116 | Other 117 | end, 118 | Forms). 119 | 120 | 121 | %% ========================================================================= 122 | %% Utility functions 123 | %% ========================================================================= 124 | 125 | gen_function(Name, Arity, QuotedClauses) 126 | when is_atom(Name) andalso 127 | is_integer(Arity) -> 128 | BoundClauses = bind(QuotedClauses), 129 | {function, 0, Name, Arity, BoundClauses}. 130 | 131 | bind(Forms) -> 132 | bind(Forms, []). 133 | 134 | bind([], Acc) -> 135 | Acc; 136 | bind([{QuotedClauses, []}| Tail], Acc) -> 137 | UnquotedClauses = forms:unquote(QuotedClauses), 138 | Acc1 = lists:append([Acc, UnquotedClauses]), 139 | bind(Tail, Acc1); 140 | bind([{QuotedClauses, Bindings}| Tail], Acc) -> 141 | UnquotedClauses = forms:unquote(QuotedClauses), 142 | BoundClauses = 143 | lists:flatmap( 144 | fun(B) -> 145 | forms:map( 146 | fun({var, Line, Var}) -> 147 | case proplists:get_value(Var, B) of 148 | undefined -> 149 | {var, Line, Var}; 150 | Value -> 151 | to_abstract(Value) 152 | end; 153 | (Other) -> 154 | Other 155 | end, 156 | UnquotedClauses) 157 | end, 158 | Bindings), 159 | Acc1 = lists:append([Acc, BoundClauses]), 160 | bind(Tail, Acc1). 161 | 162 | to_abstract(Element) -> 163 | X1 = lists:flatten(io_lib:format("~p", [Element])), 164 | [X2] = forms:to_abstract( 165 | lists:append([X1, "."])), 166 | X2. 167 | -------------------------------------------------------------------------------- /test/dummy_module.erl: -------------------------------------------------------------------------------- 1 | %%%======================================================================== 2 | %%% File: dummy_module.erl 3 | %%% 4 | %%% A dummy module used for testing. 5 | %%% 6 | %%% 7 | %%% Author: Enrique Fernandez 8 | %%% Date: November, 2014 9 | %%% 10 | %%%-- LICENSE ------------------------------------------------------------- 11 | %%% The MIT License (MIT) 12 | %%% 13 | %%% Copyright (c) 2014-2016 Enrique Fernandez 14 | %%% 15 | %%% Permission is hereby granted, free of charge, to any person obtaining 16 | %%% a copy of this software and associated documentation files (the 17 | %%% "Software"), to deal in the Software without restriction, including 18 | %%% without limitation the rights to use, copy, modify, merge, publish, 19 | %%% distribute, sublicense, and/or sell copies of the Software, 20 | %%% and to permit persons to whom the Software is furnished to do so, 21 | %%% subject to the following conditions: 22 | %%% 23 | %%% The above copyright notice and this permission notice shall be included 24 | %%% in all copies or substantial portions of the Software. 25 | %%% 26 | %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 27 | %%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 28 | %%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 29 | %%% IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 30 | %%% CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 31 | %%% TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 32 | %%% SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 33 | %%%======================================================================== 34 | -module(dummy_module). 35 | 36 | -compile({parse_transform, dummy_transform}). 37 | 38 | -export([int_to_text/1]). 39 | 40 | int_to_text(I) when is_integer(I) -> 41 | to_text(I). 42 | -------------------------------------------------------------------------------- /test/dummy_module2.erl: -------------------------------------------------------------------------------- 1 | %%%======================================================================== 2 | %%% File: dummy_module2.erl 3 | %%% 4 | %%% A dummy module used for testing. 5 | %%% 6 | %%% 7 | %%% Author: Enrique Fernandez 8 | %%% Date: November, 2014 9 | %%% 10 | %%%-- LICENSE ------------------------------------------------------------- 11 | %%% The MIT License (MIT) 12 | %%% 13 | %%% Copyright (c) 2014-2016 Enrique Fernandez 14 | %%% 15 | %%% Permission is hereby granted, free of charge, to any person obtaining 16 | %%% a copy of this software and associated documentation files (the 17 | %%% "Software"), to deal in the Software without restriction, including 18 | %%% without limitation the rights to use, copy, modify, merge, publish, 19 | %%% distribute, sublicense, and/or sell copies of the Software, 20 | %%% and to permit persons to whom the Software is furnished to do so, 21 | %%% subject to the following conditions: 22 | %%% 23 | %%% The above copyright notice and this permission notice shall be included 24 | %%% in all copies or substantial portions of the Software. 25 | %%% 26 | %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 27 | %%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 28 | %%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 29 | %%% IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 30 | %%% CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 31 | %%% TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 32 | %%% SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 33 | %%%======================================================================== 34 | -module(dummy_module2). 35 | 36 | -export([foo/0]). 37 | 38 | foo() -> 39 | _ = 1, 40 | _ = 2, 41 | _ = 3, 42 | _ = 4, 43 | _ = 5, 44 | Oops = [ oops || _ <- lists:seq(1,5) ], 45 | bar(Oops), 46 | foo. 47 | 48 | bar([]) -> 49 | oops; 50 | bar([_| Tail]) -> 51 | bar(Tail). 52 | -------------------------------------------------------------------------------- /test/dummy_transform.erl: -------------------------------------------------------------------------------- 1 | %%%======================================================================== 2 | %%% File: dummy_transform.erl 3 | %%% 4 | %%% A dummy parse transform used for testing. The parse transform 5 | %%% implemented in this module adds a to_text/1 function which converts 6 | %%% integers from 1 to 5 to its text representation (e.g. 1 becomes "one"). 7 | %%% 8 | %%% 9 | %%% Author: Enrique Fernandez 10 | %%% Date: November, 2014 11 | %%% 12 | %%%-- LICENSE ------------------------------------------------------------- 13 | %%% The MIT License (MIT) 14 | %%% 15 | %%% Copyright (c) 2014-2016 Enrique Fernandez 16 | %%% 17 | %%% Permission is hereby granted, free of charge, to any person obtaining 18 | %%% a copy of this software and associated documentation files (the 19 | %%% "Software"), to deal in the Software without restriction, including 20 | %%% without limitation the rights to use, copy, modify, merge, publish, 21 | %%% distribute, sublicense, and/or sell copies of the Software, 22 | %%% and to permit persons to whom the Software is furnished to do so, 23 | %%% subject to the following conditions: 24 | %%% 25 | %%% The above copyright notice and this permission notice shall be included 26 | %%% in all copies or substantial portions of the Software. 27 | %%% 28 | %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 29 | %%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 30 | %%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 31 | %%% IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 32 | %%% CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 33 | %%% TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 34 | %%% SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 35 | %%%======================================================================== 36 | -module(dummy_transform). 37 | 38 | -compile({parse_transform, forms_pt}). 39 | 40 | -export([parse_transform/2]). 41 | 42 | parse_transform(Forms, _Opts) -> 43 | Bindings1 = [ 44 | [{'X', 1}, {'Y', "one"}], 45 | [{'X', 2}, {'Y', "two"}], 46 | [{'X', 3}, {'Y', "three"}], 47 | [{'X', 4}, {'Y', "four"}], 48 | [{'X', 5}, {'Y', "five"}] 49 | ], 50 | ToTextFunction = 51 | forms:function(to_text, 52 | [ 53 | {fun(X) -> Y end, Bindings1}, 54 | {fun(_) -> {error, invalid_input} end, []} 55 | ]), 56 | [EOF = {eof, _}| OtherForms] = lists:reverse(Forms), 57 | lists:reverse([EOF, ToTextFunction| OtherForms]). 58 | -------------------------------------------------------------------------------- /test/forms_pt_tests.erl: -------------------------------------------------------------------------------- 1 | %%%======================================================================== 2 | %%% File: forms_pt_tests.erl 3 | %%% 4 | %%% Unit tests for the forms_pt module. 5 | %%% 6 | %%% 7 | %%% Author: Enrique Fernandez 8 | %%% Date: November, 2014 9 | %%% 10 | %%%-- LICENSE ------------------------------------------------------------- 11 | %%% The MIT License (MIT) 12 | %%% 13 | %%% Copyright (c) 2014-2016 Enrique Fernandez 14 | %%% 15 | %%% Permission is hereby granted, free of charge, to any person obtaining 16 | %%% a copy of this software and associated documentation files (the 17 | %%% "Software"), to deal in the Software without restriction, including 18 | %%% without limitation the rights to use, copy, modify, merge, publish, 19 | %%% distribute, sublicense, and/or sell copies of the Software, 20 | %%% and to permit persons to whom the Software is furnished to do so, 21 | %%% subject to the following conditions: 22 | %%% 23 | %%% The above copyright notice and this permission notice shall be included 24 | %%% in all copies or substantial portions of the Software. 25 | %%% 26 | %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 27 | %%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 28 | %%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 29 | %%% IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 30 | %%% CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 31 | %%% TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 32 | %%% SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 33 | %%%======================================================================== 34 | -module(forms_pt_tests). 35 | 36 | -include_lib("eunit/include/eunit.hrl"). 37 | 38 | function_injection_test() -> 39 | "one" = dummy_module:int_to_text(1). 40 | -------------------------------------------------------------------------------- /test/forms_test.erl: -------------------------------------------------------------------------------- 1 | %%%======================================================================== 2 | %%% forms_tests.erl 3 | %%% 4 | %%% Unit tests for the forms module. 5 | %%% 6 | %%% Author: Enrique Fernandez 7 | %%% Date: November, 2014 8 | %%% 9 | %%%-- LICENSE ------------------------------------------------------------- 10 | %%% The MIT License (MIT) 11 | %%% 12 | %%% Copyright (c) 2014 Enrique Fernandez 13 | %%% 14 | %%% Permission is hereby granted, free of charge, to any person obtaining 15 | %%% a copy of this software and associated documentation files (the 16 | %%% "Software"), to deal in the Software without restriction, including 17 | %%% without limitation the rights to use, copy, modify, merge, publish, 18 | %%% distribute, sublicense, and/or sell copies of the Software, 19 | %%% and to permit persons to whom the Software is furnished to do so, 20 | %%% subject to the following conditions: 21 | %%% 22 | %%% The above copyright notice and this permission notice shall be included 23 | %%% in all copies or substantial portions of the Software. 24 | %%% 25 | %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | %%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | %%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 28 | %%% IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 29 | %%% CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 30 | %%% TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 31 | %%% SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | %%%======================================================================== 33 | -module(forms_test). 34 | 35 | -include_lib("eunit/include/eunit.hrl"). 36 | 37 | 38 | %% ======================================================================== 39 | %% Tests 40 | %% ======================================================================== 41 | 42 | read_from_source_test() -> 43 | SourceFile = "test/dummy_module.erl", 44 | Forms = forms:read(SourceFile), 45 | {attribute, 1, file, {SourceFile, 1}} = hd(Forms). 46 | 47 | read_from_source_error_test() -> 48 | {file_not_found, "i_do_not_exist.erl"} = 49 | (catch forms:read("i_do_not_exist.erl")). 50 | 51 | read_from_binary_test() -> 52 | Forms = forms:read(dummy_module), 53 | {attribute, _Line, file, {_SrcFile, 1}} = hd(Forms). 54 | 55 | read_from_binary_error1_test() -> 56 | {module_not_found, i_do_not_exist} = 57 | (catch forms:read(i_do_not_exist)). 58 | 59 | read_from_binary_error2_test() -> 60 | %% Ensure no_debug_info module is compiled without the +debug_info 61 | %% option 62 | Dir = filename:dirname(code:which(no_debug_info)), 63 | {ok, no_debug_info} = compile:file(filename:join(Dir, "no_debug_info"), 64 | [{outdir,Dir}]), 65 | {forms_not_found, no_debug_info} = 66 | (catch forms:read(no_debug_info)). 67 | 68 | identity_transform1_test() -> 69 | Forms = forms:read(dummy_module2), 70 | Forms = forms:map(fun(Form) -> Form end, Forms). 71 | 72 | identity_transform2_test() -> 73 | Forms = forms:read(dummy_module2), 74 | Forms = forms:map(fun(Form) -> Form end, Forms, [forms_only]). 75 | 76 | reduce_test() -> 77 | Forms = forms:read(dummy_module2), 78 | 7 = forms:reduce( 79 | fun({var, _Line, '_'}, Acc) -> 80 | Acc + 1; 81 | (_Form, Acc) -> 82 | Acc 83 | end, 84 | 0, 85 | Forms). 86 | 87 | mr_test() -> 88 | Forms = forms:read(dummy_module2), 89 | {7, Forms} = forms:mr( 90 | fun({var, _Line, '_'} = Form, Acc) -> 91 | {Acc + 1, Form}; 92 | (Form, Acc) -> 93 | {Acc, Form} 94 | end, 95 | 0, 96 | Forms). 97 | 98 | reduce_next_test() -> 99 | Forms = forms:read(dummy_module2), 100 | Forms = forms:map( 101 | fun(Form) -> 102 | {next, Form} 103 | end, 104 | Forms). 105 | 106 | mr_next_test() -> 107 | Forms = forms:read(dummy_module2), 108 | {_Count, Forms} = forms:mr( 109 | fun(Form, Acc) -> 110 | {Acc + 1, {next, Form}} 111 | end, 112 | 0, 113 | Forms). 114 | 115 | map_next_test() -> 116 | Forms = forms:read(dummy_module2), 117 | Forms = forms:map( 118 | fun({call, _L, _MF, _A} = Form) -> 119 | {next, Form}; 120 | (Form) -> 121 | Form 122 | end, 123 | Forms). 124 | 125 | any_true_test() -> 126 | Forms = forms:read(dummy_module2), 127 | true = forms:any(fun({function, _, foo, 0, _}) -> true; 128 | (_) -> false end, 129 | Forms). 130 | 131 | any_false_test() -> 132 | Forms = forms:read(dummy_module2), 133 | false = forms:any(fun({function, _, doesnotexist, 2, _}) -> true; 134 | (_) -> false end, 135 | Forms). 136 | 137 | 138 | %% ======================================================================== 139 | %% Auxiliary functions 140 | %% ======================================================================== 141 | 142 | erl_from_beam(Module) -> 143 | BeamFile = code:which(Module), 144 | filename:dirname(BeamFile) ++ atom_to_list(Module) ++ ".erl". 145 | -------------------------------------------------------------------------------- /test/no_debug_info.erl: -------------------------------------------------------------------------------- 1 | -module(no_debug_info). 2 | --------------------------------------------------------------------------------