├── ebin └── .gitignore ├── rebar ├── doc ├── erlang.png ├── edoc-info ├── README.md ├── stylesheet.css ├── parse_trans_pp.md ├── ct_expand.md ├── parse_trans_mod.md ├── parse_trans_codegen.md ├── exprecs.md └── parse_trans.md ├── .gitignore ├── examples ├── t.erl ├── ex1.erl ├── lc.erl ├── test_pt.erl ├── ex_pmod.erl ├── test.erl ├── t_ex.erl ├── ex_gen_module.erl ├── test_exprecs.erl ├── test_transform_mod.erl ├── ex_gproc_send_xform.erl ├── test_exprecs_vsns.erl ├── Makefile ├── ct_expand_test.erl ├── exprecs_eunit.erl ├── ex_codegen.erl └── pmod.erl ├── Makefile ├── rebar.config ├── README.md ├── include ├── exprecs.hrl └── codegen.hrl └── src ├── parse_trans.app.src ├── parse_trans_pp.erl ├── parse_trans_mod.erl ├── ct_expand.erl ├── parse_trans_codegen.erl ├── parse_trans.erl └── exprecs.erl /ebin/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esl/parse_trans/HEAD/rebar -------------------------------------------------------------------------------- /doc/erlang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esl/parse_trans/HEAD/doc/erlang.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | examples/*.beam 2 | examples/*.xf* 3 | ebin 4 | *~ 5 | */*~ 6 | .eunit 7 | -------------------------------------------------------------------------------- /examples/t.erl: -------------------------------------------------------------------------------- 1 | -module(t). 2 | -export([f/1]). 3 | 4 | f(X) -> 5 | (fun(X1) -> 6 | X1 7 | end)(X). 8 | -------------------------------------------------------------------------------- /examples/ex1.erl: -------------------------------------------------------------------------------- 1 | -module(ex1). 2 | -export([add/2]). 3 | 4 | add(X,Y) -> 5 | X + Y. 6 | 7 | int() -> 8 | int. 9 | -------------------------------------------------------------------------------- /examples/lc.erl: -------------------------------------------------------------------------------- 1 | -module(lc). 2 | -export([f/1]). 3 | 4 | f(X) -> 5 | [fun(_) -> 6 | erlang:now() 7 | end || {Y1,Y2} <- [{1,a},{2,b}]]. 8 | -------------------------------------------------------------------------------- /doc/edoc-info: -------------------------------------------------------------------------------- 1 | {application,parse_trans}. 2 | {packages,[]}. 3 | {modules,[ct_expand,exprecs,parse_trans,parse_trans_codegen,parse_trans_mod, 4 | parse_trans_pp]}. 5 | -------------------------------------------------------------------------------- /examples/test_pt.erl: -------------------------------------------------------------------------------- 1 | -module(test_pt). 2 | 3 | -export([parse_transform/2]). 4 | 5 | parse_transform(Forms, _Options) -> 6 | io:fwrite("Forms = ~p~n", [Forms]), 7 | Forms. 8 | 9 | -------------------------------------------------------------------------------- /examples/ex_pmod.erl: -------------------------------------------------------------------------------- 1 | -module(ex_pmod). 2 | 3 | -compile({parse_transform, pmod}). 4 | 5 | -pmod_vars(['A', 'B']). 6 | 7 | -pmod_funs([a/1, 8 | b/2]). 9 | 10 | a(X) -> 11 | X. 12 | 13 | b(X,Y) -> 14 | {X,Y,A,B}. 15 | -------------------------------------------------------------------------------- /examples/test.erl: -------------------------------------------------------------------------------- 1 | %% This is a test module 2 | -module(test). 3 | -compile({parse_transform, test_pt}). 4 | 5 | -export([f/1]). 6 | -export_records([r]). 7 | -record(r, {a = [1,2,3] :: [integer()], 8 | b}). 9 | 10 | -spec f(X) -> X. 11 | f(X) -> 12 | X. 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REBAR=$(shell which rebar || echo ./rebar) 2 | 3 | .PHONY: rel all deps clean 4 | 5 | all: deps compile 6 | 7 | compile: 8 | $(REBAR) compile 9 | 10 | deps: 11 | ./rebar get-deps 12 | 13 | clean: 14 | $(REBAR) clean 15 | 16 | test: 17 | $(REBAR) eunit 18 | 19 | doc: 20 | $(REBAR) doc 21 | 22 | -------------------------------------------------------------------------------- /examples/t_ex.erl: -------------------------------------------------------------------------------- 1 | -module(t_ex). 2 | 3 | -record(r, {a,b}). 4 | 5 | -export(['sel-r'/1]). 6 | 7 | -spec 'sel-r'(a | b) -> 8 | {fun((#r{}) -> any()), fun((any(), #r{}) -> #r{})}. 9 | 'sel-r'(a) -> 10 | {fun(#r{a = A}) -> 11 | A 12 | end, 13 | fun(X, #r{} = R) -> 14 | R#r{a = X} 15 | end}. 16 | -------------------------------------------------------------------------------- /examples/ex_gen_module.erl: -------------------------------------------------------------------------------- 1 | -module(ex_gen_module). 2 | -compile(export_all). 3 | 4 | -compile({parse_transform, parse_trans_codegen}). 5 | 6 | f() -> 7 | codegen:gen_module(test, [{render,0}, {source, 0}], 8 | [ 9 | {render, fun() -> 10 | x end}, 11 | {source, fun() -> 12 | ok end} 13 | ]). 14 | -------------------------------------------------------------------------------- /examples/test_exprecs.erl: -------------------------------------------------------------------------------- 1 | -module(test_exprecs). 2 | 3 | -pt_renumber(true). 4 | -pt_log_forms(true). 5 | 6 | -export([f/0]). 7 | 8 | -compile({parse_transform, exprecs}). 9 | 10 | -record(r, {a = 0 :: integer(), b = 0 :: integer(), c = 0 :: integer()}). 11 | -record(s, {a}). 12 | -record(t, {}). 13 | 14 | -export_records([r, s, t]). 15 | 16 | 17 | f() -> 18 | foo. 19 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | %% -*- erlang -*- 2 | {erl_first_files, ["src/parse_trans.erl", 3 | "src/parse_trans_pp.erl", 4 | "src/parse_trans_codegen.erl"]}. 5 | 6 | {erl_opts, [debug_info]}. 7 | {xref_checks, [undefined_function_calls]}. 8 | {deps, [{edown, ".*", {git, "git://github.com/esl/edown.git", "HEAD"}}]}. 9 | {edoc_opts, [{doclet, edown_doclet}, 10 | {top_level_readme, 11 | {"./README.md", 12 | "http://github.com/esl/parse_trans"}}]}. 13 | -------------------------------------------------------------------------------- /examples/test_transform_mod.erl: -------------------------------------------------------------------------------- 1 | -module(test_transform_mod). 2 | -export([ex1/0]). 3 | 4 | -include("codegen.hrl"). 5 | 6 | ex1() -> 7 | parse_trans_mod:transform_module( 8 | ex1, [fun(Fs, _Os) -> 9 | parse_trans:export_function(int, 0, Fs) 10 | end, 11 | fun transform_ex1/2], [{pt_pp_src,true}]). 12 | 13 | transform_ex1(Forms, _Opts) -> 14 | NewF = codegen:gen_function(add, fun(A, B) -> 15 | A - B 16 | end), 17 | parse_trans:replace_function(add, 2, NewF, Forms). 18 | -------------------------------------------------------------------------------- /examples/ex_gproc_send_xform.erl: -------------------------------------------------------------------------------- 1 | -module(ex_gproc_send_xform). 2 | -export([parse_transform/2]). 3 | 4 | 5 | parse_transform(Forms, _Options) -> 6 | parse_trans:light_transform(fun do_transform/1, Forms). 7 | 8 | do_transform({'op', L, '!', Lhs, Rhs}) -> 9 | [NewLhs] = parse_trans:light_transform(fun do_transform/1, [Lhs]), 10 | [NewRhs] = parse_trans:light_transform(fun do_transform/1, [Rhs]), 11 | {call, L, {remote, L, {atom, L, gproc}, {atom, L, send}}, 12 | [NewLhs, NewRhs]}; 13 | do_transform(_) -> 14 | continue. 15 | 16 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | #The parse_trans application# 4 | 5 | 6 | ##Modules## 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
ct_expand
exprecs
parse_trans
parse_trans_codegen
parse_trans_mod
parse_trans_pp
16 | 17 | -------------------------------------------------------------------------------- /examples/test_exprecs_vsns.erl: -------------------------------------------------------------------------------- 1 | -module(test_exprecs_vsns). 2 | 3 | -export([f/0]). 4 | -export_records([r]). 5 | 6 | -compile({parse_transform, exprecs}). 7 | 8 | -record(r, {a,b,c}). 9 | -record(r__1_2, {a,b}). 10 | -record(r__1_1, {a,b,c,d}). 11 | 12 | 13 | f() -> 14 | io:fwrite("'#info-r'(fields) -> ~p~n", ['#info-r'(fields)]), 15 | io:fwrite("'#info-r__1_1'(fields)' -> ~p (not exported)~n", 16 | ['#info-r__1_1'(fields)]), 17 | io:fwrite("'#info-r__1_2'(fields)' -> ~p (not exported)~n", 18 | ['#info-r__1_2'(fields)]), 19 | io:fwrite("'#convert-'(\"1_1\", {r,1,2,3,4}) -> ~p~n", 20 | ['#convert-'("1_1", {r,1,2,3,4})]), 21 | io:fwrite("'#convert-'(\"1_2\", {r,1,2}) -> ~p~n", 22 | ['#convert-'("1_2", {r,1,2})]). 23 | 24 | -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | SOURCES := $(wildcard *.erl) 2 | HEADERS := $(wildcard *.hrl) 3 | BEAMS := $(patsubst %.erl,%.beam,$(SOURCES)) 4 | 5 | .PHONY: all clean 6 | 7 | all: $(BEAMS) 8 | 9 | test: $(APPLICATION) $(TEST_BEAMS) ../util/run_test.beam 10 | @echo Running tests 11 | @erl -pa ../util/ -pa ../ebin/ -pa test/ -noinput -s run_test run 12 | 13 | test_exprecs.beam: ../ebin/exprecs.beam test_exprecs.erl 14 | @echo Compiling test_exprecs.erl 15 | @erlc +debug_info -pa ../ebin -pa . -I ../include test_exprecs.erl 16 | 17 | test.beam: test_pt.beam 18 | ex_pmod.beam: pmod.beam 19 | 20 | %.beam: %.erl $(HEADERS) 21 | @echo Compiling $< 22 | @erlc +debug_info -pa ../ebin -pa . -I ../include $< 23 | 24 | clean: 25 | @echo Cleaning 26 | @rm -f *.beam 27 | @rm -f *~ 28 | @rm -f *.xfm 29 | @rm -f *.xforms 30 | 31 | -------------------------------------------------------------------------------- /examples/ct_expand_test.erl: -------------------------------------------------------------------------------- 1 | -module(ct_expand_test). 2 | 3 | -export([f/0]). 4 | 5 | -compile({parse_transform, ct_expand}). 6 | -pt_pp_src(true). 7 | 8 | f() -> 9 | ct_expand:term( 10 | [{a, 1}, 11 | {b, ct_expand:term( 12 | [{ba, 1}, 13 | {bb, ct_expand:term(2)}])}]). 14 | 15 | %% expand a term which calls a local function - even one which uses a fun reference. 16 | g() -> 17 | ct_expand:term(zip([1,2], [a,b])). 18 | 19 | h() -> 20 | ct_expand:term(wrap(my_fun())). 21 | 22 | i() -> 23 | ct_expand:term(gb_trees:insert(a_fun, my_fun2(), gb_trees:empty())). 24 | 25 | zip([H1|T1], [H2|T2]) -> 26 | F = my_fun2(), 27 | [{F(H1),F(H2)} | zip(T1, T2)]; 28 | zip([], []) -> 29 | []. 30 | 31 | wrap(X) -> 32 | {X}. 33 | 34 | my_fun() -> 35 | fun() -> foo end. 36 | 37 | my_fun2() -> 38 | fun wrap/1. 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | #The parse_trans application# 4 | 5 | 6 | ##Modules## 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
ct_expand
exprecs
parse_trans
parse_trans_codegen
parse_trans_mod
parse_trans_pp
16 | 17 | -------------------------------------------------------------------------------- /include/exprecs.hrl: -------------------------------------------------------------------------------- 1 | %%% The contents of this file are subject to the Erlang Public License, 2 | %%% Version 1.1, (the "License"); you may not use this file except in 3 | %%% compliance with the License. You may obtain a copy of the License at 4 | %%% http://www.erlang.org/EPLICENSE 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 8 | %% the License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is exprecs-0.2. 12 | %% 13 | %% Copyright (c) 2010 Erlang Solutions Ltd. 14 | %% 15 | %% Contributor(s): ______________________________________. 16 | 17 | %%------------------------------------------------------------------- 18 | %% File : exprecs.hrl 19 | %% @author : Ulf Wiger 20 | %% @end 21 | %% Description : 22 | %% 23 | %% Created : 25 Feb 2010 by Ulf Wiger 24 | %%------------------------------------------------------------------- 25 | -compile({parse_transform, exprecs}). 26 | -------------------------------------------------------------------------------- /include/codegen.hrl: -------------------------------------------------------------------------------- 1 | %%% The contents of this file are subject to the Erlang Public License, 2 | %%% Version 1.1, (the "License"); you may not use this file except in 3 | %%% compliance with the License. You may obtain a copy of the License at 4 | %%% http://www.erlang.org/EPLICENSE 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 8 | %% the License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is parse_trans-2.0. 12 | %% 13 | %% Copyright (c) 2010 Erlang Solutions Ltd. 14 | %% 15 | %% Contributor(s): ______________________________________. 16 | 17 | %%------------------------------------------------------------------- 18 | %% File : codegen.hrl 19 | %% @author : Ulf Wiger 20 | %% @end 21 | %% Description : 22 | %% 23 | %% Created : 25 Feb 2010 by Ulf Wiger 24 | %%------------------------------------------------------------------- 25 | -compile({parse_transform, parse_trans_codegen}). 26 | -------------------------------------------------------------------------------- /doc/stylesheet.css: -------------------------------------------------------------------------------- 1 | /* standard EDoc style sheet */ 2 | body { 3 | font-family: Verdana, Arial, Helvetica, sans-serif; 4 | margin-left: .25in; 5 | margin-right: .2in; 6 | margin-top: 0.2in; 7 | margin-bottom: 0.2in; 8 | color: #000000; 9 | background-color: #ffffff; 10 | } 11 | h1,h2 { 12 | margin-left: -0.2in; 13 | } 14 | div.navbar { 15 | background-color: #add8e6; 16 | padding: 0.2em; 17 | } 18 | h2.indextitle { 19 | padding: 0.4em; 20 | background-color: #add8e6; 21 | } 22 | h3.function,h3.typedecl { 23 | background-color: #add8e6; 24 | padding-left: 1em; 25 | } 26 | div.spec { 27 | margin-left: 2em; 28 | background-color: #eeeeee; 29 | } 30 | a.module,a.package { 31 | text-decoration:none 32 | } 33 | a.module:hover,a.package:hover { 34 | background-color: #eeeeee; 35 | } 36 | ul.definitions { 37 | list-style-type: none; 38 | } 39 | ul.index { 40 | list-style-type: none; 41 | background-color: #eeeeee; 42 | } 43 | 44 | /* 45 | * Minor style tweaks 46 | */ 47 | ul { 48 | list-style-type: square; 49 | } 50 | table { 51 | border-collapse: collapse; 52 | } 53 | td { 54 | padding: 3 55 | } 56 | -------------------------------------------------------------------------------- /src/parse_trans.app.src: -------------------------------------------------------------------------------- 1 | %%% -*- erlang -*- 2 | %%% The contents of this file are subject to the Erlang Public License, 3 | %%% Version 1.1, (the "License"); you may not use this file except in 4 | %%% compliance with the License. You may obtain a copy of the License at 5 | %%% http://www.erlang.org/EPLICENSE 6 | %%% 7 | %%% Software distributed under the License is distributed on an "AS IS" 8 | %%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 9 | %%% the License for the specific language governing rights and limitations 10 | %%% under the License. 11 | %%% 12 | %%% The Original Code is exprecs-0.2. 13 | %%% 14 | %%% The Initial Developer of the Original Code is Ericsson AB. 15 | %%% Portions created by Ericsson are Copyright (C), 2006, Ericsson AB. 16 | %%% All Rights Reserved. 17 | %%% 18 | %%% Contributor(s): ______________________________________. 19 | 20 | %%% @author Ulf Wiger 21 | %%% @doc This is a container for parse_trans modules. 22 | %%% @end 23 | {application, parse_trans, 24 | [{description, "Parse transform library"}, 25 | {vsn, git}, 26 | {registered, []}, 27 | {applications, [kernel, stdlib, syntax_tools]}, 28 | {env, []} 29 | ]}. 30 | 31 | -------------------------------------------------------------------------------- /examples/exprecs_eunit.erl: -------------------------------------------------------------------------------- 1 | -module(exprecs_eunit). 2 | -include_lib("eunit/include/eunit.hrl"). 3 | 4 | -export([module_test/0]). 5 | 6 | 7 | module_test() -> 8 | M = test_exprecs, 9 | RecNames = lists:concat( 10 | [Rs || {export_records, Rs} <- M:module_info(attributes)]), 11 | io:fwrite("RecNames = ~p~n", [RecNames]), 12 | false = M:'#is_record-'([]), 13 | false = M:'#is_record-'([], []), 14 | [test_record(R, M) || R <- RecNames]. 15 | 16 | test_record(R, M) -> 17 | Rec = M:'#new-'(R), 18 | Fields = M:'#info-'(R), 19 | FieldCount = length(Fields), 20 | InfoF = list_to_existing_atom("#info-" ++ atom_to_list(R)), 21 | ?assertEqual(FieldCount+1, M:InfoF(size)), 22 | ?assertEqual(true, M:'#is_record-'(Rec)), 23 | ?assertMatch({R,true}, {R, M:'#is_record-'(R, Rec)}), 24 | NewF = list_to_existing_atom("#new-" ++ atom_to_list(R)), 25 | L = lists:seq(1, FieldCount), 26 | Vals = lists:zip(Fields, L), 27 | Rec1 = M:NewF(Vals), 28 | Rec1 = M:'#set-'(Vals, Rec), 29 | Rec1 = M:'#fromlist-'(Vals, M:'#new-'(R)), 30 | L = M:'#get-'(Fields, Rec1), 31 | ?assertError(bad_record_op, M:'#get-'(17,Rec1)), 32 | PosL = lists:seq(2, FieldCount + 1), 33 | PosL = [M:'#pos-'(R, A) || A <- Fields], 34 | ?assertEqual(0, M:'#pos-'(R, bad_attr_name)). 35 | -------------------------------------------------------------------------------- /examples/ex_codegen.erl: -------------------------------------------------------------------------------- 1 | -module(ex_codegen). 2 | 3 | -compile({parse_transform, parse_trans_codegen}). 4 | 5 | -export([f/1, g/2, h/0, i/0, j/2, k/0, k/1, gen/2, fs/0]). 6 | 7 | %%-pt_pp_forms(true). 8 | -pt_pp_src(true). 9 | 10 | f(Name) -> 11 | codegen:gen_function( 12 | Name, 13 | fun(1,2,3) -> 14 | foo; 15 | (A,B,C) -> 16 | {A,B,C} 17 | end). 18 | 19 | 20 | 21 | g(Name, V) -> 22 | codegen:gen_function( 23 | Name, 24 | fun(L) -> 25 | member({'$var',V}, L) 26 | end). 27 | 28 | h() -> 29 | codegen:gen_function( 30 | funny, 31 | fun() -> 32 | fun gen/2 33 | end). 34 | 35 | i() -> 36 | codegen:exprs(fun(X) -> 37 | case X of 1 -> 38 | is_1; 39 | Other -> 40 | {is_other,Other} 41 | end 42 | end). 43 | 44 | j(Name, Form) -> 45 | codegen:gen_function( 46 | Name, 47 | fun(L) -> 48 | member({'$form',Form}, L) 49 | end). 50 | 51 | x() -> 52 | [(fun(X) -> 53 | X 54 | end)(X1) || X1 <- [1,2]]. 55 | 56 | k() -> 57 | codegen:gen_function( 58 | lcf, 59 | [fun({'$var',X}) -> 60 | {'$var',Y} 61 | end || {X, Y} <- [{1,a},{2,b},{3,c}]]). 62 | 63 | k(L) -> 64 | codegen:gen_function( 65 | lcf, 66 | [fun({'$var',X}) -> 67 | {'$var',Y} 68 | end || {X, Y} <- L]). 69 | 70 | gen(Name, X) -> 71 | codegen:gen_function(Name, fun(L) -> lists:member({'$var',X}, L) end). 72 | 73 | fs() -> 74 | V = local_V, 75 | codegen:gen_functions( 76 | [{foo, fun() -> 77 | foo 78 | end}, 79 | {h1, fun h/0}, 80 | {g1, fun g/2}, 81 | {bar, fun() -> 82 | bar 83 | end}]). 84 | -------------------------------------------------------------------------------- /doc/parse_trans_pp.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | #Module parse_trans_pp# 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | 9 | Generic parse transform library for Erlang. 10 | 11 | 12 | 13 | __Authors:__ : Ulf Wiger ([`ulf@feuerlabs.com`](mailto:ulf@feuerlabs.com)). 14 | 15 | ##Description## 16 | 17 | 18 | This module contains some useful utility functions for inspecting 19 | the results of parse transforms or code generation. 20 | The function `main/1` is called from escript, and can be used to 21 | pretty-print debug info in a .beam file from a Linux shell.Using e.g. the following bash alias: 22 |
23 |    alias pp='escript $PARSE_TRANS_ROOT/ebin/parse_trans_pp.beam'
24 | 25 | 26 | a file could be pretty-printed using the following command:`$ pp ex_codegen.beam | less` 27 | 28 | ##Function Index## 29 | 30 | 31 |
main/1
pp_beam/1 32 | Reads debug_info from the beam file Beam and returns a string containing 33 | the pretty-printed corresponding erlang source code.
pp_beam/2 34 | Reads debug_info from the beam file Beam and pretty-prints it as 35 | Erlang source code, storing it in the file Out.
pp_src/2Pretty-prints the erlang source code corresponding to Forms into Out.
36 | 37 | 38 | 39 | 40 | ##Function Details## 41 | 42 | 43 | 44 | ###main/1## 45 | 46 | 47 | 48 | 49 |
main(X1::[string()]) -> any()
50 |

51 | 52 | 53 | 54 | 55 | ###pp_beam/1## 56 | 57 | 58 | 59 | 60 |
pp_beam(Beam::filename()) -> string() | {error, Reason}
61 |

62 | 63 | 64 | 65 | 66 | 67 | Reads debug_info from the beam file Beam and returns a string containing 68 | the pretty-printed corresponding erlang source code. 69 | 70 | ###pp_beam/2## 71 | 72 | 73 | 74 | 75 |
pp_beam(Beam::filename(), Out::filename()) -> ok | {error, Reason}
76 |

77 | 78 | 79 | 80 | 81 | 82 | Reads debug_info from the beam file Beam and pretty-prints it as 83 | Erlang source code, storing it in the file Out. 84 | 85 | ###pp_src/2## 86 | 87 | 88 | 89 | 90 |
pp_src(Forms0::Forms, Out::filename()) -> ok
91 |

92 | 93 | 94 | 95 | 96 | Pretty-prints the erlang source code corresponding to Forms into Out 97 | -------------------------------------------------------------------------------- /doc/ct_expand.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | #Module ct_expand# 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | 10 | Compile-time expansion utility. 11 | 12 | 13 | 14 | __Authors:__ : Ulf Wiger ([`ulf@feuerlabs.com`](mailto:ulf@feuerlabs.com)). 15 | 16 | ##Description## 17 | 18 | 19 | This module serves as an example of parse_trans-based transforms, 20 | but might also be a useful utility in its own right. 21 | The transform searches for calls to the pseudo-function 22 | `ct_expand:term(Expr)`, and then replaces the call site with the 23 | result of evaluating `Expr` at compile-time. 24 | 25 | For example, the line 26 | 27 | `ct_expand:term(lists:sort([3,5,2,1,4]))` 28 | 29 | would be expanded at compile-time to `[1,2,3,4,5]`. 30 | 31 | ct_expand has now been extended to also evaluate calls to local functions. 32 | See examples/ct_expand_test.erl for some examples. 33 | 34 | A debugging facility exists: passing the option {ct_expand_trace, Flags} as an option, 35 | or adding a compiler attribute -ct_expand_trace(Flags) will enable a form of call trace.`Flags` can be `[]` (no trace) or `[F]`, where `F` is `c` (call trace), 36 | `r` (return trace), or `x` (exception trace)'. 37 | 38 | 39 | 40 | ##Data Types## 41 | 42 | 43 | 44 | 45 | ###form()## 46 | 47 | 48 | 49 |
form() = any()
50 | 51 | 52 | 53 | ###forms()## 54 | 55 | 56 | 57 |
forms() = [form()]
58 | 59 | 60 | 61 | ###options()## 62 | 63 | 64 | 65 |
options() = [{atom(), any()}]
66 | 67 | 68 | ##Function Index## 69 | 70 | 71 |
extract_fun/3
lfun_rewrite/2
parse_transform/2
72 | 73 | 74 | 75 | 76 | ##Function Details## 77 | 78 | 79 | 80 | ###extract_fun/3## 81 | 82 | 83 | 84 | 85 | `extract_fun(Name, Arity, Forms) -> any()` 86 | 87 | 88 | 89 | ###lfun_rewrite/2## 90 | 91 | 92 | 93 | 94 | `lfun_rewrite(Exprs, Forms) -> any()` 95 | 96 | 97 | 98 | ###parse_transform/2## 99 | 100 | 101 | 102 | 103 |
parse_transform(Forms::forms(), Options::options()) -> forms()
104 |

105 | 106 | 107 | -------------------------------------------------------------------------------- /doc/parse_trans_mod.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | #Module parse_trans_mod# 4 | * [Data Types](#types) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | ##Data Types## 15 | 16 | 17 | 18 | 19 | ###compile_options()## 20 | 21 | 22 | 23 |
compile_options() = [term()]
24 | 25 | 26 | 27 | ###erlang_form()## 28 | 29 | 30 | 31 |
erlang_form() = term()
32 | 33 | 34 | ##Function Index## 35 | 36 | 37 |
abstract_code/1
beam_file/1
compile_and_load_forms/1
compile_and_load_forms/2
compile_options/1
rename_module/2
transform_module/3
38 | 39 | 40 | 41 | 42 | ##Function Details## 43 | 44 | 45 | 46 | ###abstract_code/1## 47 | 48 | 49 | 50 | 51 |
abstract_code(BeamFile::binary()) -> erlang_form()
52 |

53 | 54 | 55 | 56 | 57 | ###beam_file/1## 58 | 59 | 60 | 61 | 62 |
beam_file(Module::module()) -> binary()
63 |

64 | 65 | 66 | 67 | 68 | ###compile_and_load_forms/1## 69 | 70 | 71 | 72 | 73 |
compile_and_load_forms(AbsCode::erlang_form()) -> ok
74 |

75 | 76 | 77 | 78 | 79 | ###compile_and_load_forms/2## 80 | 81 | 82 | 83 | 84 |
compile_and_load_forms(AbsCode::erlang_form(), Opts::compile_options()) -> ok
85 |

86 | 87 | 88 | 89 | 90 | ###compile_options/1## 91 | 92 | 93 | 94 | 95 |
compile_options(BeamFile::binary() | module()) -> compile_options()
96 |

97 | 98 | 99 | 100 | 101 | ###rename_module/2## 102 | 103 | 104 | 105 | 106 |
rename_module(T::erlang_form(), NewName::module()) -> erlang_form()
107 |

108 | 109 | 110 | 111 | 112 | ###transform_module/3## 113 | 114 | 115 | 116 | 117 | `transform_module(Mod, PT, Options) -> any()` 118 | 119 | -------------------------------------------------------------------------------- /examples/pmod.erl: -------------------------------------------------------------------------------- 1 | -module(pmod). 2 | 3 | -export([parse_transform/2]). 4 | 5 | -import(erl_syntax, [attribute_name/1, attribute_arguments/1, 6 | list_elements/1, 7 | atom_value/1, tuple_elements/1, integer_value/1]). 8 | 9 | -record(st, {vars = [], 10 | funs = ordsets:new()}). 11 | 12 | parse_transform(Forms, Opts) -> 13 | case proplists:get_bool(trace, Opts) of 14 | true -> 15 | dbg:tracer(), 16 | dbg:tpl(?MODULE,x), 17 | dbg:p(all,[c]); 18 | false -> 19 | ok 20 | end, 21 | C = parse_trans:initial_context(Forms, Opts), 22 | St = parse_trans:do_inspect(fun inspect/4, #st{}, Forms, C), 23 | Res = parse_trans:revert( 24 | export_funs(wrap_funs(create_new_f(Forms, St, C), St, C), St, C)), 25 | parse_trans:optionally_pretty_print(Res, Opts, C), 26 | Res. 27 | 28 | inspect(attribute, Form, _C, Acc) -> 29 | case attr_name(Form) of 30 | pmod_vars -> 31 | [] = Acc#st.vars, %% assertion - should be a proper check 32 | VarNames = [atom_value(V) || 33 | V <- list_elements(hd(attribute_arguments(Form)))], 34 | {false, Acc#st{vars = VarNames}}; 35 | pmod_funs -> 36 | Fs = lists:foldl(fun(E, Acc1) -> 37 | [Fun,Ay] = tuple_elements(E), 38 | ordsets:add_element( 39 | {atom_value(Fun), integer_value(Ay)}, 40 | Acc1) 41 | end, Acc#st.funs, 42 | list_elements(hd(attribute_arguments(Form)))), 43 | {false, Acc#st{funs = Fs}}; 44 | _ -> 45 | {false, Acc} 46 | end; 47 | inspect(_, _, _, Acc) -> 48 | {false, Acc}. 49 | 50 | attr_name(F) -> 51 | atom_value(attribute_name(F)). 52 | 53 | create_new_f(Forms, #st{vars = Vs}, C) -> 54 | Arity = length(Vs), 55 | Form = {function, 1, new, Arity, 56 | [{clause, 1, 57 | [{var,1,V} || V <- Vs], 58 | [], 59 | [{tuple, 1, [{atom, 1, parse_trans:context(module, C)}, 60 | {tuple, 1, [{var,1,V} || V <- Vs]}]} 61 | ]} 62 | ]}, 63 | parse_trans:do_insert_forms(above, [Form], Forms, C). 64 | 65 | wrap_funs(Forms, #st{vars = Vs, funs = Fs}, C) -> 66 | Mod = parse_trans:context(module, C), 67 | lists:foldl( 68 | fun({F,A}, Acc) -> 69 | {NewForms, _} = 70 | parse_trans:do_transform( 71 | fun(T,Form,C1,Acc1) -> 72 | wrap_fun(T,Form,C1,Acc1,F,A,Vs,Mod) 73 | end, false, Acc, C), 74 | NewForms 75 | end, Forms, Fs). 76 | 77 | wrap_fun(function, Form, _, Acc, F, A, Vs,Mod) -> 78 | case erl_syntax:revert(Form) of 79 | {function, L, F, A, Cs} -> 80 | {{function, L, F, A + 1, 81 | [{clause, Lc, 82 | Args ++ [{tuple,1,[{atom,1,Mod}, 83 | {tuple,1, 84 | fix_vars(Vs, F, A, Clause)}]}], Gs, B} 85 | || {clause, Lc, Args, Gs, B} = Clause <- Cs]}, 86 | false, Acc}; 87 | _ -> 88 | {Form, false, Acc} 89 | end; 90 | wrap_fun(_, Form, _, Acc, _, _, _, _) -> 91 | {Form, false, Acc}. 92 | 93 | fix_vars(Vars, F, A, Clause) -> 94 | %% erl_syntax_lib:variables/1 doesn't seem to work with just a clause... 95 | Used = sets:to_list(erl_syntax_lib:variables({function,1,F,A,[Clause]})), 96 | [{var, 1, fix_var(V, Used)} || V <- Vars]. 97 | 98 | fix_var(V, Used) -> 99 | case lists:member(V, Used) of 100 | true -> V; 101 | false -> 102 | list_to_atom("_" ++ atom_to_list(V)) 103 | end. 104 | 105 | 106 | export_funs(Forms, #st{vars = Vs, funs = Fs}, C) -> 107 | New = [{attribute,1,export,[{new,length(Vs)}]}, 108 | {attribute,1,export,[{F,A+1} || {F,A} <- Fs]}], 109 | parse_trans:do_insert_forms(above, New, Forms, C). 110 | -------------------------------------------------------------------------------- /src/parse_trans_pp.erl: -------------------------------------------------------------------------------- 1 | %%% The contents of this file are subject to the Erlang Public License, 2 | %%% Version 1.1, (the "License"); you may not use this file except in 3 | %%% compliance with the License. You may obtain a copy of the License at 4 | %%% http://www.erlang.org/EPLICENSE 5 | %%% 6 | %%% Software distributed under the License is distributed on an "AS IS" 7 | %%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 8 | %%% the License for the specific language governing rights and limitations 9 | %%% under the License. 10 | %%% 11 | %%% The Original Code is exprecs-0.2. 12 | %%% 13 | %%% The Initial Developer of the Original Code is Ericsson AB. 14 | %%% Portions created by Ericsson are Copyright (C), 2006, Ericsson AB. 15 | %%% All Rights Reserved. 16 | %%% 17 | %%% Contributor(s): ______________________________________. 18 | 19 | %%%------------------------------------------------------------------- 20 | %%% File : parse_trans_pp.erl 21 | %%% @author : Ulf Wiger 22 | %%% @end 23 | %%% Description : 24 | %%% 25 | %%% Created : 3 Aug 2010 by Ulf Wiger 26 | %%%------------------------------------------------------------------- 27 | 28 | %%% @doc Generic parse transform library for Erlang. 29 | %%% 30 | %%% This module contains some useful utility functions for inspecting 31 | %%% the results of parse transforms or code generation. 32 | %%% The function `main/1' is called from escript, and can be used to 33 | %%% pretty-print debug info in a .beam file from a Linux shell. 34 | %%% 35 | %%% Using e.g. the following bash alias: 36 | %%%
 37 | %%% alias pp='escript $PARSE_TRANS_ROOT/ebin/parse_trans_pp.beam'
 38 | %%% 
39 | %%% a file could be pretty-printed using the following command: 40 | %%% 41 | %%% `$ pp ex_codegen.beam | less' 42 | %%% @end 43 | 44 | -module(parse_trans_pp). 45 | 46 | -export([ 47 | pp_src/2, 48 | pp_beam/1, pp_beam/2 49 | ]). 50 | 51 | -export([main/1]). 52 | 53 | 54 | -spec main([string()]) -> any(). 55 | main([F]) -> 56 | pp_beam(F). 57 | 58 | 59 | %% @spec (Forms, Out::filename()) -> ok 60 | %% 61 | %% @doc Pretty-prints the erlang source code corresponding to Forms into Out 62 | %% 63 | -spec pp_src(parse_trans:forms(), file:filename()) -> 64 | ok. 65 | pp_src(Forms0, F) -> 66 | Forms = epp:restore_typed_record_fields(revert(Forms0)), 67 | Str = [io_lib:fwrite("~s~n", 68 | [lists:flatten([erl_pp:form(Fm) || 69 | Fm <- Forms])])], 70 | file:write_file(F, list_to_binary(Str)). 71 | 72 | %% @spec (Beam::filename()) -> string() | {error, Reason} 73 | %% 74 | %% @doc 75 | %% Reads debug_info from the beam file Beam and returns a string containing 76 | %% the pretty-printed corresponding erlang source code. 77 | %% @end 78 | -spec pp_beam(file:filename()) -> ok | {error, any()}. 79 | pp_beam(Beam) -> 80 | case pp_beam_to_str(Beam) of 81 | {ok, Str} -> 82 | io:put_chars(Str); 83 | Other -> 84 | Other 85 | end. 86 | 87 | %% @spec (Beam::filename(), Out::filename()) -> ok | {error, Reason} 88 | %% 89 | %% @doc 90 | %% Reads debug_info from the beam file Beam and pretty-prints it as 91 | %% Erlang source code, storing it in the file Out. 92 | %% @end 93 | %% 94 | -spec pp_beam(file:filename(), file:filename()) -> ok | {error,any()}. 95 | pp_beam(F, Out) -> 96 | case pp_beam_to_str(F) of 97 | {ok, Str} -> 98 | file:write_file(Out, list_to_binary(Str)); 99 | Other -> 100 | Other 101 | end. 102 | 103 | pp_beam_to_str(F) -> 104 | case beam_lib:chunks(F, [abstract_code]) of 105 | {ok, {_, [{abstract_code,{_,AC0}}]}} -> 106 | AC = epp:restore_typed_record_fields(AC0), 107 | {ok, lists:flatten( 108 | %% io_lib:fwrite("~s~n", [erl_prettypr:format( 109 | %% erl_syntax:form_list(AC))]) 110 | io_lib:fwrite("~s~n", [lists:flatten( 111 | [erl_pp:form(Form) || 112 | Form <- AC])]) 113 | )}; 114 | Other -> 115 | {error, Other} 116 | end. 117 | 118 | -spec revert(parse_trans:forms()) -> 119 | parse_trans:forms(). 120 | revert(Tree) -> 121 | [erl_syntax:revert(T) || T <- lists:flatten(Tree)]. 122 | -------------------------------------------------------------------------------- /src/parse_trans_mod.erl: -------------------------------------------------------------------------------- 1 | %%============================================================================ 2 | %% Copyright 2011 Erlang Solutions Ltd. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%============================================================================ 16 | %% 17 | %% Based on meck_mod.erl from http://github.com/esl/meck.git 18 | %% Original author: Adam Lindberg 19 | %% 20 | -module(parse_trans_mod). 21 | %% Interface exports 22 | -export([transform_module/3]). 23 | 24 | -export([abstract_code/1]). 25 | -export([beam_file/1]). 26 | -export([compile_and_load_forms/1]). 27 | -export([compile_and_load_forms/2]). 28 | -export([compile_options/1]). 29 | -export([rename_module/2]). 30 | 31 | %% Types 32 | -type erlang_form() :: term(). 33 | -type compile_options() :: [term()]. 34 | 35 | %%============================================================================ 36 | %% Interface exports 37 | %%============================================================================ 38 | 39 | transform_module(Mod, PT, Options) -> 40 | Forms = abstract_code(beam_file(Mod)), 41 | Context = parse_trans:initial_context(Forms, Options), 42 | PTMods = if is_atom(PT) -> [PT]; 43 | is_function(PT, 2) -> [PT]; 44 | is_list(PT) -> PT 45 | end, 46 | Transformed = lists:foldl(fun(PTx, Fs) when is_function(PTx, 2) -> 47 | PTx(Fs, Options); 48 | (PTMod, Fs) -> 49 | PTMod:parse_transform(Fs, Options) 50 | end, Forms, PTMods), 51 | parse_trans:optionally_pretty_print(Transformed, Options, Context), 52 | compile_and_load_forms(Transformed, Options). 53 | 54 | 55 | -spec abstract_code(binary()) -> erlang_form(). 56 | abstract_code(BeamFile) -> 57 | case beam_lib:chunks(BeamFile, [abstract_code]) of 58 | {ok, {_, [{abstract_code, {raw_abstract_v1, Forms}}]}} -> 59 | Forms; 60 | {ok, {_, [{abstract_code, no_abstract_code}]}} -> 61 | erlang:error(no_abstract_code) 62 | end. 63 | 64 | -spec beam_file(module()) -> binary(). 65 | beam_file(Module) -> 66 | % code:which/1 cannot be used for cover_compiled modules 67 | case code:get_object_code(Module) of 68 | {_, Binary, _Filename} -> Binary; 69 | error -> throw({object_code_not_found, Module}) 70 | end. 71 | 72 | -spec compile_and_load_forms(erlang_form()) -> ok. 73 | compile_and_load_forms(AbsCode) -> compile_and_load_forms(AbsCode, []). 74 | 75 | -spec compile_and_load_forms(erlang_form(), compile_options()) -> ok. 76 | compile_and_load_forms(AbsCode, Opts) -> 77 | case compile:forms(AbsCode, Opts) of 78 | {ok, ModName, Binary} -> 79 | load_binary(ModName, Binary); 80 | {ok, ModName, Binary, _Warnings} -> 81 | load_binary(ModName, Binary) 82 | end. 83 | 84 | -spec compile_options(binary() | module()) -> compile_options(). 85 | compile_options(BeamFile) when is_binary(BeamFile) -> 86 | case beam_lib:chunks(BeamFile, [compile_info]) of 87 | {ok, {_, [{compile_info, Info}]}} -> 88 | proplists:get_value(options, Info); 89 | _ -> 90 | [] 91 | end; 92 | compile_options(Module) -> 93 | proplists:get_value(options, Module:module_info(compile)). 94 | 95 | -spec rename_module(erlang_form(), module()) -> erlang_form(). 96 | rename_module([{attribute, Line, module, _OldName}|T], NewName) -> 97 | [{attribute, Line, module, NewName}|T]; 98 | rename_module([H|T], NewName) -> 99 | [H|rename_module(T, NewName)]. 100 | 101 | %%============================================================================== 102 | %% Internal functions 103 | %%============================================================================== 104 | 105 | load_binary(Name, Binary) -> 106 | case code:load_binary(Name, "", Binary) of 107 | {module, Name} -> ok; 108 | {error, Reason} -> exit({error_loading_module, Name, Reason}) 109 | end. 110 | -------------------------------------------------------------------------------- /doc/parse_trans_codegen.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | #Module parse_trans_codegen# 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | 9 | Parse transform for code generation pseduo functions. 10 | 11 | 12 | 13 | __Authors:__ : Ulf Wiger ([`ulf@feuerlabs.com`](mailto:ulf@feuerlabs.com)). 14 | 15 | ##Description## 16 | 17 | 18 | ... 19 | 20 | 21 | ##Function Index## 22 | 23 | 24 |
format_error/1
parse_transform/2 25 | Searches for calls to pseudo functions in the module codegen, 26 | and converts the corresponding erlang code to a data structure 27 | representing the abstract form of that code.
28 | 29 | 30 | 31 | 32 | ##Function Details## 33 | 34 | 35 | 36 | ###format_error/1## 37 | 38 | 39 | 40 | 41 | `format_error(E) -> any()` 42 | 43 | 44 | 45 | ###parse_transform/2## 46 | 47 | 48 | 49 | 50 |
parse_transform(Forms, Options) -> NewForms
51 |

52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | Searches for calls to pseudo functions in the module `codegen`, 60 | and converts the corresponding erlang code to a data structure 61 | representing the abstract form of that code. 62 | 63 | The purpose of these functions is to let the programmer write 64 | the actual code that is to be generated, rather than manually 65 | writing abstract forms, which is more error prone and cannot be 66 | checked by the compiler until the generated module is compiled. 67 | 68 | Supported functions: 69 | 70 | ##gen_function/2## 71 | 72 | 73 | Usage: `codegen:gen_function(Name, Fun)` 74 | 75 | Substitutes the abstract code for a function with name `Name` 76 | and the same behaviour as `Fun`.`Fun` can either be a anonymous `fun`, which is then converted to 77 | a named function, or it can be an `implicit fun`, e.g. 78 | `fun is_member/2`. In the latter case, the referenced function is fetched 79 | and converted to an abstract form representation. It is also renamed 80 | so that the generated function has the name `Name`. 81 | 82 | 83 | 84 | Another alternative is to wrap a fun inside a list comprehension, e.g. 85 |
 86 |   f(Name, L) ->
 87 |       codegen:gen_function(
 88 |           Name,
 89 |           [ fun({'$var',X}) ->
 90 |                {'$var', Y}
 91 |             end || {X, Y} <- L ]).
92 | 93 | 94 | 95 | Calling the above with `f(foo, [{1,a},{2,b},{3,c}])` will result in 96 | generated code corresponding to: 97 |
 98 |   foo(1) -> a;
 99 |   foo(2) -> b;
100 |   foo(3) -> c.
101 | 102 | ##gen_functions/1## 103 | 104 | 105 | Takes a list of `{Name, Fun}` tuples and produces a list of abstract 106 | data objects, just as if one had written 107 | `[codegen:gen_function(N1,F1),codegen:gen_function(N2,F2),...]`. 108 | 109 | ##exprs/1## 110 | 111 | 112 | Usage: `codegen:exprs(Fun)` 113 | 114 | `Fun` is either an anonymous function, or an implicit fun with only one 115 | function clause. This "function" takes the body of the fun and produces 116 | a data type representing the abstract form of the list of expressions in 117 | the body. The arguments of the function clause are ignored, but can be 118 | used to ensure that all necessary variables are known to the compiler. 119 | 120 | ##gen_module/3## 121 | 122 | 123 | Generates abstract forms for a complete module definition. 124 | 125 | Usage: `codegen:gen_module(ModuleName, Exports, Functions)` 126 | 127 | `ModuleName` is either an atom or a `{'$var', V}` reference. 128 | 129 | `Exports` is a list of `{Function, Arity}` tuples. 130 | 131 | `Functions` is a list of `{Name, Fun}` tuples analogous to that for 132 | `gen_functions/1`. 133 | 134 | ##Variable substitution## 135 | 136 | 137 | It is possible to do some limited expansion (importing a value 138 | bound at compile-time), using the construct `{'$var', V}`, where 139 | `V` is a bound variable in the scope of the call to `gen_function/2`.Example: 140 |
141 |   gen(Name, X) ->
142 |      codegen:gen_function(Name, fun(L) -> lists:member({'$var',X}, L) end).
After transformation, calling `gen(contains_17, 17)` will yield the 143 | abstract form corresponding to: 144 |
145 |   contains_17(L) ->
146 |      lists:member(17, L).
147 | 148 | ##Form substitution## 149 | 150 | 151 | It is possible to inject abstract forms, using the construct 152 | `{'$form', F}`, where `F` is bound to a parsed form in 153 | the scope of the call to `gen_function/2`.Example: 154 |
155 |   gen(Name, F) ->
156 |      codegen:gen_function(Name, fun(X) -> X =:= {'$form',F} end).
After transformation, calling `gen(is_foo, {atom,0,foo})` will yield the 157 | abstract form corresponding to: 158 |
159 |   is_foo(X) ->
160 |      X =:= foo.
-------------------------------------------------------------------------------- /src/ct_expand.erl: -------------------------------------------------------------------------------- 1 | %%% The contents of this file are subject to the Erlang Public License, 2 | %%% Version 1.1, (the "License"); you may not use this file except in 3 | %%% compliance with the License. You may obtain a copy of the License at 4 | %%% http://www.erlang.org/EPLICENSE 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 8 | %% the License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is exprecs-0.2. 12 | %% 13 | %% The Initial Developer of the Original Code is Ericsson AB. 14 | %% Portions created by Ericsson are Copyright (C), 2006, Ericsson AB. 15 | %% All Rights Reserved. 16 | %% 17 | %% Contributor(s): ______________________________________. 18 | 19 | %%------------------------------------------------------------------- 20 | %% File : ct_expand.erl 21 | %% @author : Ulf Wiger 22 | %% @end 23 | %% Description : 24 | %% 25 | %% Created : 7 Apr 2010 by Ulf Wiger 26 | %%------------------------------------------------------------------- 27 | 28 | %% @doc Compile-time expansion utility 29 | %% 30 | %% This module serves as an example of parse_trans-based transforms, 31 | %% but might also be a useful utility in its own right. 32 | %% The transform searches for calls to the pseudo-function 33 | %% `ct_expand:term(Expr)', and then replaces the call site with the 34 | %% result of evaluating `Expr' at compile-time. 35 | %% 36 | %% For example, the line 37 | %% 38 | %% `ct_expand:term(lists:sort([3,5,2,1,4]))' 39 | %% 40 | %% would be expanded at compile-time to `[1,2,3,4,5]'. 41 | %% 42 | %% ct_expand has now been extended to also evaluate calls to local functions. 43 | %% See examples/ct_expand_test.erl for some examples. 44 | %% 45 | %% A debugging facility exists: passing the option {ct_expand_trace, Flags} as an option, 46 | %% or adding a compiler attribute -ct_expand_trace(Flags) will enable a form of call trace. 47 | %% 48 | %% `Flags' can be `[]' (no trace) or `[F]', where `F' is `c' (call trace), 49 | %% `r' (return trace), or `x' (exception trace)'. 50 | %% 51 | %% @end 52 | -module(ct_expand). 53 | -export([parse_transform/2]). 54 | 55 | -export([extract_fun/3, 56 | lfun_rewrite/2]). 57 | 58 | -type form() :: any(). 59 | -type forms() :: [form()]. 60 | -type options() :: [{atom(), any()}]. 61 | 62 | 63 | -spec parse_transform(forms(), options()) -> 64 | forms(). 65 | parse_transform(Forms, Options) -> 66 | Trace = ct_trace_opt(Options, Forms), 67 | case parse_trans:depth_first(fun(T,F,C,A) -> 68 | xform_fun(T,F,C,A,Forms, Trace) 69 | end, [], Forms, Options) of 70 | {error, Es} -> 71 | Es ++ Forms; 72 | {NewForms, _} -> 73 | parse_trans:revert(NewForms) 74 | end. 75 | 76 | ct_trace_opt(Options, Forms) -> 77 | case proplists:get_value(ct_expand_trace, Options) of 78 | undefined -> 79 | case [Opt || {attribute,_,ct_expand_trace,Opt} <- Forms] of 80 | [] -> 81 | []; 82 | [_|_] = L -> 83 | lists:last(L) 84 | end; 85 | Flags when is_list(Flags) -> 86 | Flags 87 | end. 88 | 89 | xform_fun(application, Form, _Ctxt, Acc, Forms, Trace) -> 90 | MFA = erl_syntax_lib:analyze_application(Form), 91 | case MFA of 92 | {?MODULE, {term, 1}} -> 93 | LFH = fun(Name, Args, Bs) -> 94 | eval_lfun( 95 | extract_fun(Name, length(Args), Forms), 96 | Args, Bs, Forms, Trace) 97 | end, 98 | Args = erl_syntax:application_arguments(Form), 99 | RevArgs = parse_trans:revert(Args), 100 | case erl_eval:exprs(RevArgs, [], {eval, LFH}) of 101 | {value, Value,[]} -> 102 | {abstract(Value), Acc}; 103 | Other -> 104 | parse_trans:error(cannot_evaluate,?LINE, 105 | [{expr, RevArgs}, 106 | {error, Other}]) 107 | end; 108 | _ -> 109 | {Form, Acc} 110 | end; 111 | xform_fun(_, Form, _Ctxt, Acc, _, _) -> 112 | {Form, Acc}. 113 | 114 | extract_fun(Name, Arity, Forms) -> 115 | case [F_ || {function,_,N_,A_,_Cs} = F_ <- Forms, 116 | N_ == Name, A_ == Arity] of 117 | [] -> 118 | erlang:error({undef, [{Name, Arity}]}); 119 | [FForm] -> 120 | FForm 121 | end. 122 | 123 | eval_lfun({function,L,F,_,Clauses}, Args, Bs, Forms, Trace) -> 124 | try 125 | begin 126 | {ArgsV, Bs1} = lists:mapfoldl( 127 | fun(A, Bs_) -> 128 | {value,AV,Bs1_} = 129 | erl_eval:expr(A, Bs_, lfh(Forms, Trace)), 130 | {abstract(AV), Bs1_} 131 | end, Bs, Args), 132 | Expr = {call, L, {'fun', L, {clauses, lfun_rewrite(Clauses, Forms)}}, ArgsV}, 133 | call_trace(Trace =/= [], L, F, ArgsV), 134 | {value, Ret, _} = 135 | erl_eval:expr(Expr, erl_eval:new_bindings(), lfh(Forms, Trace)), 136 | ret_trace(lists:member(r, Trace) orelse lists:member(x, Trace), 137 | L, F, Args, Ret), 138 | %% restore bindings 139 | {value, Ret, Bs1} 140 | end 141 | catch 142 | error:Err -> 143 | exception_trace(lists:member(x, Trace), L, F, Args, Err), 144 | error(Err) 145 | end. 146 | 147 | lfh(Forms, Trace) -> 148 | {eval, fun(Name, As, Bs1) -> 149 | eval_lfun( 150 | extract_fun(Name, length(As), Forms), 151 | As, Bs1, Forms, Trace) 152 | end}. 153 | 154 | call_trace(false, _, _, _) -> ok; 155 | call_trace(true, L, F, As) -> 156 | io:fwrite("ct_expand (~w): call ~s~n", [L, pp_function(F, As)]). 157 | 158 | pp_function(F, []) -> 159 | atom_to_list(F) ++ "()"; 160 | pp_function(F, [A|As]) -> 161 | lists:flatten([atom_to_list(F), "(", 162 | [io_lib:fwrite("~w", [erl_parse:normalise(A)]) | 163 | [[",", io_lib:fwrite("~w", [erl_parse:normalise(A_)])] || A_ <- As]], 164 | ")"]). 165 | 166 | ret_trace(false, _, _, _, _) -> ok; 167 | ret_trace(true, L, F, Args, Res) -> 168 | io:fwrite("ct_expand (~w): returned from ~w/~w: ~w~n", 169 | [L, F, length(Args), Res]). 170 | 171 | exception_trace(false, _, _, _, _) -> ok; 172 | exception_trace(true, L, F, Args, Err) -> 173 | io:fwrite("ct_expand (~w): exception from ~w/~w: ~p~n", [L, F, length(Args), Err]). 174 | 175 | 176 | lfun_rewrite(Exprs, Forms) -> 177 | parse_trans:plain_transform( 178 | fun({'fun',L,{function,F,A}}) -> 179 | {function,_,_,_,Cs} = extract_fun(F, A, Forms), 180 | {'fun',L,{clauses, Cs}}; 181 | (_) -> 182 | continue 183 | end, Exprs). 184 | 185 | 186 | %% abstract/1 - modified from erl_eval:abstract/1: 187 | -type abstract_expr() :: term(). 188 | -spec abstract(Data) -> AbsTerm when 189 | Data :: term(), 190 | AbsTerm :: abstract_expr(). 191 | abstract(T) when is_function(T) -> 192 | case erlang:fun_info(T, module) of 193 | {module, erl_eval} -> 194 | case erl_eval:fun_data(T) of 195 | {fun_data, _Imports, Clauses} -> 196 | {'fun', 0, {clauses, Clauses}}; 197 | false -> 198 | erlang:error(function_clause) % mimicking erl_parse:abstract(T) 199 | end; 200 | _ -> 201 | erlang:error(function_clause) 202 | end; 203 | abstract(T) when is_integer(T) -> {integer,0,T}; 204 | abstract(T) when is_float(T) -> {float,0,T}; 205 | abstract(T) when is_atom(T) -> {atom,0,T}; 206 | abstract([]) -> {nil,0}; 207 | abstract(B) when is_bitstring(B) -> 208 | {bin, 0, [abstract_byte(Byte, 0) || Byte <- bitstring_to_list(B)]}; 209 | abstract([C|T]) when is_integer(C), 0 =< C, C < 256 -> 210 | abstract_string(T, [C]); 211 | abstract([H|T]) -> 212 | {cons,0,abstract(H),abstract(T)}; 213 | abstract(Tuple) when is_tuple(Tuple) -> 214 | {tuple,0,abstract_list(tuple_to_list(Tuple))}. 215 | 216 | abstract_string([C|T], String) when is_integer(C), 0 =< C, C < 256 -> 217 | abstract_string(T, [C|String]); 218 | abstract_string([], String) -> 219 | {string, 0, lists:reverse(String)}; 220 | abstract_string(T, String) -> 221 | not_string(String, abstract(T)). 222 | 223 | not_string([C|T], Result) -> 224 | not_string(T, {cons, 0, {integer, 0, C}, Result}); 225 | not_string([], Result) -> 226 | Result. 227 | 228 | abstract_list([H|T]) -> 229 | [abstract(H)|abstract_list(T)]; 230 | abstract_list([]) -> 231 | []. 232 | 233 | abstract_byte(Byte, Line) when is_integer(Byte) -> 234 | {bin_element, Line, {integer, Line, Byte}, default, default}; 235 | abstract_byte(Bits, Line) -> 236 | Sz = bit_size(Bits), 237 | <> = Bits, 238 | {bin_element, Line, {integer, Line, Val}, {integer, Line, Sz}, default}. 239 | 240 | -------------------------------------------------------------------------------- /doc/exprecs.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | #Module exprecs# 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | 10 | Parse transform for generating record access functions. 11 | 12 | 13 | 14 | __Authors:__ : Ulf Wiger ([`ulf.wiger@ericsson.com`](mailto:ulf.wiger@ericsson.com)). 15 | 16 | ##Description## 17 | 18 | 19 | This parse transform can be used to reduce compile-time 20 | dependencies in large systems. 21 | 22 | 23 | In the old days, before records, Erlang programmers often wrote 24 | access functions for tuple data. This was tedious and error-prone. 25 | The record syntax made this easier, but since records were implemented 26 | fully in the pre-processor, a nasty compile-time dependency was 27 | introduced. 28 | 29 | 30 | This module automates the generation of access functions for 31 | records. While this method cannot fully replace the utility of 32 | pattern matching, it does allow a fair bit of functionality on 33 | records without the need for compile-time dependencies. 34 | 35 | 36 | Whenever record definitions need to be exported from a module, 37 | inserting a compiler attribute, 38 | `export_records([RecName|...])` causes this transform 39 | to lay out access functions for the exported records:As an example, consider the following module: 40 |
 41 |   -module(test_exprecs).
 42 |   -export([f/0]).
 43 |   -compile({parse_transform, exprecs}).
 44 |   -record(r, {a = 0 :: integer(),
 45 |               b = 0 :: integer(),
 46 |               c = 0 :: integer()}).
 47 |   -record(s,{a}).
 48 |   -export_records([r,s]).
 49 |   f() ->
 50 |       {new,'#new-r'([])}.
51 | 52 | Compiling this (assuming exprecs is in the path) will produce the 53 | following code.
 54 |   -module(test_exprecs).
 55 |   -compile({pt_pp_src,true}).
 56 |   -export([f/0]).
 57 |   -record(r,{a = 0 :: integer(),b = 0 :: integer(),c = 0 :: integer()}).
 58 |   -record(s,{a}).
 59 |   -export_records([r,s]).
 60 |   -export(['#exported_records-'/0,
 61 |            '#new-'/1,
 62 |            '#info-'/1,
 63 |            '#info-'/2,
 64 |            '#pos-'/2,
 65 |            '#is_record-'/1,
 66 |            '#is_record-'/2,
 67 |            '#get-'/2,
 68 |            '#set-'/2,
 69 |            '#fromlist-'/2,
 70 |            '#lens-'/2,
 71 |            '#new-r'/0,
 72 |            '#new-r'/1,
 73 |            '#get-r'/2,
 74 |            '#set-r'/2,
 75 |            '#pos-r'/1,
 76 |            '#fromlist-r'/1,
 77 |            '#fromlist-r'/2,
 78 |            '#info-r'/1,
 79 |            '#lens-r'/1,
 80 |            '#new-s'/0,
 81 |            '#new-s'/1,
 82 |            '#get-s'/2,
 83 |            '#set-s'/2,
 84 |            '#pos-s'/1,
 85 |            '#fromlist-s'/1,
 86 |            '#fromlist-s'/2,
 87 |            '#info-s'/1,
 88 |            '#lens-s'/1]).
 89 |   -type '#prop-r'() :: {a, integer()} | {b, integer()} | {c, integer()}.
 90 |   -type '#attr-r'() :: a | b | c.
 91 |   -type '#prop-s'() :: {a, any()}.
 92 |   -type '#attr-s'() :: a.
 93 |   -spec '#exported_records-'() -> [r | s].
 94 |   '#exported_records-'() ->
 95 |       [r,s].
 96 |   -spec '#new-'(r) -> #r{};
 97 |                (s) -> #s{}.
 98 |   '#new-'(r) ->
 99 |       '#new-r'();
100 |   '#new-'(s) ->
101 |       '#new-s'().
102 |   -spec '#info-'(r) -> [a | b | c];
103 |                 (s) -> [a].
104 |   '#info-'(RecName) ->
105 |       '#info-'(RecName, fields).
106 |   -spec '#info-'(r, size) -> 4;
107 |                 (r, fields) -> [a | b | c];
108 |                 (s, size) -> 2;
109 |                 (s, fields) -> [a].
110 |   '#info-'(r, Info) ->
111 |       '#info-r'(Info);
112 |   '#info-'(s, Info) ->
113 |       '#info-s'(Info).
114 |   -spec '#pos-'(r, a) -> 1;
115 |                (r, b) -> 2;
116 |                (r, c) -> 3;
117 |                (s, a) -> 1.
118 |   '#pos-'(r, Attr) ->
119 |       '#pos-r'(Attr);
120 |   '#pos-'(s, Attr) ->
121 |       '#pos-s'(Attr).
122 |   -spec '#is_record-'(any()) -> boolean().
123 |   '#is_record-'(X) ->
124 |       if
125 |           is_record(X, r) ->
126 |               true;
127 |           is_record(X, s) ->
128 |               true;
129 |           true ->
130 |               false
131 |       end.
132 |   -spec '#is_record-'(any(), any()) -> boolean().
133 |   '#is_record-'(s, Rec) when tuple_size(Rec) == 2, element(1, Rec) == s ->
134 |       true;
135 |   '#is_record-'(r, Rec) when tuple_size(Rec) == 4, element(1, Rec) == r ->
136 |       true;
137 |   '#is_record-'(_, _) ->
138 |       false.
139 |   -spec '#get-'(a, #r{}) -> integer();
140 |                (b, #r{}) -> integer();
141 |                (c, #r{}) -> integer();
142 |                (a, #s{}) -> any();
143 |                (['#attr-r'()], #r{}) -> [integer()];
144 |                (['#attr-s'()], #s{}) -> [any()].
145 |   '#get-'(Attrs, Rec) when is_record(Rec, r) ->
146 |       '#get-r'(Attrs, Rec);
147 |   '#get-'(Attrs, Rec) when is_record(Rec, s) ->
148 |       '#get-s'(Attrs, Rec).
149 |   -spec '#set-'(['#prop-r'()], #r{}) -> #r{};
150 |                (['#prop-s'()], #s{}) -> #s{}.
151 |   '#set-'(Vals, Rec) when is_record(Rec, r) ->
152 |       '#set-r'(Vals, Rec);
153 |   '#set-'(Vals, Rec) when is_record(Rec, s) ->
154 |       '#set-s'(Vals, Rec).
155 |   -spec '#fromlist-'(['#prop-r'()], #r{}) -> #r{};
156 |                     (['#prop-s'()], #s{}) -> #s{}.
157 |   '#fromlist-'(Vals, Rec) when is_record(Rec, r) ->
158 |       '#fromlist-r'(Vals, Rec);
159 |   '#fromlist-'(Vals, Rec) when is_record(Rec, s) ->
160 |       '#fromlist-s'(Vals, Rec).
161 |   -spec '#lens-'('#prop-r'(), r) ->
162 |                    {fun((#r{}) -> any()), fun((any(), #r{}) -> #r{})};
163 |                ('#prop-s'(), s) ->
164 |                    {fun((#s{}) -> any()), fun((any(), #s{}) -> #s{})}.
165 |   '#lens-'(Attr, r) ->
166 |       '#lens-r'(Attr);
167 |   '#lens-'(Attr, s) ->
168 |       '#lens-s'(Attr).
169 |   -spec '#new-r'() -> #r{}.
170 |   '#new-r'() ->
171 |       #r{}.
172 |   -spec '#new-r'(['#prop-r'()]) -> #r{}.
173 |   '#new-r'(Vals) ->
174 |       '#set-r'(Vals, #r{}).
175 |   -spec '#get-r'(a, #r{}) -> integer();
176 |                 (b, #r{}) -> integer();
177 |                 (c, #r{}) -> integer();
178 |                 (['#attr-r'()], #r{}) -> [integer()].
179 |   '#get-r'(Attrs, R) when is_list(Attrs) ->
180 |       [
181 |        '#get-r'(A, R) ||
182 |            A <- Attrs
183 |       ];
184 |   '#get-r'(a, R) ->
185 |       R#r.a;
186 |   '#get-r'(b, R) ->
187 |       R#r.b;
188 |   '#get-r'(c, R) ->
189 |       R#r.c;
190 |   '#get-r'(Attr, R) ->
191 |       error(bad_record_op, ['#get-r',Attr,R]).
192 |   -spec '#set-r'(['#prop-r'()], #r{}) -> #r{}.
193 |   '#set-r'(Vals, Rec) ->
194 |       F = fun([], R, _F1) ->
195 |                  R;
196 |              ([{a,V}|T], R, F1) when is_list(T) ->
197 |                  F1(T, R#r{a = V}, F1);
198 |              ([{b,V}|T], R, F1) when is_list(T) ->
199 |                  F1(T, R#r{b = V}, F1);
200 |              ([{c,V}|T], R, F1) when is_list(T) ->
201 |                  F1(T, R#r{c = V}, F1);
202 |              (Vs, R, _) ->
203 |                  error(bad_record_op, ['#set-r',Vs,R])
204 |           end,
205 |       F(Vals, Rec, F).
206 |   -spec '#fromlist-r'(['#prop-r'()]) -> #r{}.
207 |   '#fromlist-r'(Vals) when is_list(Vals) ->
208 |       '#fromlist-r'(Vals, '#new-r'()).
209 |   -spec '#fromlist-r'(['#prop-r'()], #r{}) -> #r{}.
210 |   '#fromlist-r'(Vals, Rec) ->
211 |       AttrNames = [{a,2},{b,3},{c,4}],
212 |       F = fun([], R, _F1) ->
213 |                  R;
214 |              ([{H,Pos}|T], R, F1) when is_list(T) ->
215 |                  case lists:keyfind(H, 1, Vals) of
216 |                      false ->
217 |                          F1(T, R, F1);
218 |                      {_,Val} ->
219 |                          F1(T, setelement(Pos, R, Val), F1)
220 |                  end
221 |           end,
222 |       F(AttrNames, Rec, F).
223 |   -spec '#pos-r'('#attr-r'() | atom()) -> integer().
224 |   '#pos-r'(a) ->
225 |       2;
226 |   '#pos-r'(b) ->
227 |       3;
228 |   '#pos-r'(c) ->
229 |       4;
230 |   '#pos-r'(A) when is_atom(A) ->
231 |       0.
232 |   -spec '#info-r'(fields) -> [a | b | c];
233 |                  (size) -> 3.
234 |   '#info-r'(fields) ->
235 |       record_info(fields, r);
236 |   '#info-r'(size) ->
237 |       record_info(size, r).
238 |   -spec '#lens-r'('#prop-r'()) ->
239 |                     {fun((#r{}) -> any()), fun((any(), #r{}) -> #r{})}.
240 |   '#lens-r'(a) ->
241 |       {fun(R) ->
242 |               '#get-r'(a, R)
243 |        end,
244 |        fun(X, R) ->
245 |               '#set-r'([{a,X}], R)
246 |        end};
247 |   '#lens-r'(b) ->
248 |       {fun(R) ->
249 |               '#get-r'(b, R)
250 |        end,
251 |        fun(X, R) ->
252 |               '#set-r'([{b,X}], R)
253 |        end};
254 |   '#lens-r'(c) ->
255 |       {fun(R) ->
256 |               '#get-r'(c, R)
257 |        end,
258 |        fun(X, R) ->
259 |               '#set-r'([{c,X}], R)
260 |        end};
261 |   '#lens-r'(Attr) ->
262 |       error(bad_record_op, ['#lens-r',Attr]).
263 |   -spec '#new-s'() -> #s{}.
264 |   '#new-s'() ->
265 |       #s{}.
266 |   -spec '#new-s'(['#prop-s'()]) -> #s{}.
267 |   '#new-s'(Vals) ->
268 |       '#set-s'(Vals, #s{}).
269 |   -spec '#get-s'(a, #s{}) -> any();
270 |                 (['#attr-s'()], #s{}) -> [any()].
271 |   '#get-s'(Attrs, R) when is_list(Attrs) ->
272 |       [
273 |        '#get-s'(A, R) ||
274 |            A <- Attrs
275 |       ];
276 |   '#get-s'(a, R) ->
277 |       R#s.a;
278 |   '#get-s'(Attr, R) ->
279 |       error(bad_record_op, ['#get-s',Attr,R]).
280 |   -spec '#set-s'(['#prop-s'()], #s{}) -> #s{}.
281 |   '#set-s'(Vals, Rec) ->
282 |       F = fun([], R, _F1) ->
283 |                  R;
284 |              ([{a,V}|T], R, F1) when is_list(T) ->
285 |                  F1(T, R#s{a = V}, F1);
286 |              (Vs, R, _) ->
287 |                  error(bad_record_op, ['#set-s',Vs,R])
288 |           end,
289 |       F(Vals, Rec, F).
290 |   -spec '#fromlist-s'(['#prop-s'()]) -> #s{}.
291 |   '#fromlist-s'(Vals) when is_list(Vals) ->
292 |       '#fromlist-s'(Vals, '#new-s'()).
293 |   -spec '#fromlist-s'(['#prop-s'()], #s{}) -> #s{}.
294 |   '#fromlist-s'(Vals, Rec) ->
295 |       AttrNames = [{a,2}],
296 |       F = fun([], R, _F1) ->
297 |                  R;
298 |              ([{H,Pos}|T], R, F1) when is_list(T) ->
299 |                  case lists:keyfind(H, 1, Vals) of
300 |                      false ->
301 |                          F1(T, R, F1);
302 |                      {_,Val} ->
303 |                          F1(T, setelement(Pos, R, Val), F1)
304 |                  end
305 |           end,
306 |       F(AttrNames, Rec, F).
307 |   -spec '#pos-s'('#attr-s'() | atom()) -> integer().
308 |   '#pos-s'(a) ->
309 |       2;
310 |   '#pos-s'(A) when is_atom(A) ->
311 |       0.
312 |   -spec '#info-s'(fields) -> [a];
313 |                  (size) -> 1.
314 |   '#info-s'(fields) ->
315 |       record_info(fields, s);
316 |   '#info-s'(size) ->
317 |       record_info(size, s).
318 |   -spec '#lens-s'('#prop-s'()) ->
319 |                     {fun((#s{}) -> any()), fun((any(), #s{}) -> #s{})}.
320 |   '#lens-s'(a) ->
321 |       {fun(R) ->
322 |               '#get-s'(a, R)
323 |        end,
324 |        fun(X, R) ->
325 |               '#set-s'([{a,X}], R)
326 |        end};
327 |   '#lens-s'(Attr) ->
328 |       error(bad_record_op, ['#lens-s', Attr]).
329 |   f() ->
330 |       {new,'#new-r'([])}.
331 | 332 | It is possible to modify the naming rules of exprecs, through the use 333 | of the following attributes (example reflecting the current rules):
334 |   -exprecs_prefix(["#", operation, "-"]).
335 |   -exprecs_fname([prefix, record]).
336 |   -exprecs_vfname([fname, "__", version]).
The lists must contain strings or any of the following control atoms: 337 | 338 | * in `exprecs_prefix`: `operation` 339 | 340 | * in `exprecs_fname`: `operation`, `record`, `prefix` 341 | 342 | * in `exprecs_vfname`: `operation`, `record`, `prefix`, `fname`, `version` 343 | 344 | 345 | 346 | Exprecs will substitute the control atoms with the string values of the 347 | corresponding items. The result will then be flattened and converted to an 348 | atom (a valid function or type name).`operation` is one of: 349 | 350 | 351 | 352 |
new
353 | 354 | 355 | 356 |
Creates a new record
357 | 358 | 359 | 360 | 361 |
get
362 | 363 | 364 | 365 |
Retrieves given attribute values from a record
366 | 367 | 368 | 369 | 370 |
set
371 | 372 | 373 | 374 |
Sets given attribute values in a record
375 | 376 | 377 | 378 | 379 |
fromlist
380 | 381 | 382 | 383 |
Creates a record from a key-value list
384 | 385 | 386 | 387 | 388 |
info
389 | 390 | 391 | 392 |
Equivalent to record_info/2
393 | 394 | 395 | 396 | 397 |
pos
398 | 399 | 400 | 401 |
Returns the position of a given attribute
402 | 403 | 404 | 405 | 406 |
is_record
407 | 408 | 409 | 410 |
Tests if a value is a specific record
411 | 412 | 413 | 414 | 415 |
convert
416 | 417 | 418 | 419 |
Converts an old record to the current version
420 | 421 | 422 | 423 | 424 |
prop
425 | 426 | 427 | 428 |
Used only in type specs
429 | 430 | 431 | 432 | 433 |
attr
434 | 435 | 436 | 437 |
Used only in type specs
438 | 439 | 440 | 441 | 442 |
lens
443 | 444 | 445 | 446 |
Returns a 'lens' (an accessor pair) as described in 447 | http://github.com/jlouis/erl-lenses
448 | 449 | 450 | 451 | 452 | 453 | 454 | ##Data Types## 455 | 456 | 457 | 458 | 459 | ###form()## 460 | 461 | 462 | 463 |
form() = any()
464 | 465 | 466 | 467 | ###forms()## 468 | 469 | 470 | 471 |
forms() = [form()]
472 | 473 | 474 | 475 | ###options()## 476 | 477 | 478 | 479 |
options() = [{atom(), any()}]
480 | 481 | 482 | ##Function Index## 483 | 484 | 485 |
parse_transform/2
486 | 487 | 488 | 489 | 490 | ##Function Details## 491 | 492 | 493 | 494 | ###parse_transform/2## 495 | 496 | 497 | 498 | 499 |
parse_transform(Forms::forms(), Options::options()) -> forms()
500 |

501 | 502 | 503 | -------------------------------------------------------------------------------- /src/parse_trans_codegen.erl: -------------------------------------------------------------------------------- 1 | %%% The contents of this file are subject to the Erlang Public License, 2 | %%% Version 1.1, (the "License"); you may not use this file except in 3 | %%% compliance with the License. You may obtain a copy of the License at 4 | %%% http://www.erlang.org/EPLICENSE 5 | %%% 6 | %%% Software distributed under the License is distributed on an "AS IS" 7 | %%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 8 | %%% the License for the specific language governing rights and limitations 9 | %%% under the License. 10 | %%% 11 | %%% The Original Code is exprecs-0.2. 12 | %%% 13 | %%% The Initial Developer of the Original Code is Ericsson AB. 14 | %%% Portions created by Ericsson are Copyright (C), 2006, Ericsson AB. 15 | %%% All Rights Reserved. 16 | %%% 17 | %%% Contributor(s): ______________________________________. 18 | 19 | %%%------------------------------------------------------------------- 20 | %%% File : parse_trans_codegen.erl 21 | %%% @author : Ulf Wiger 22 | %%% @end 23 | %%% Description : 24 | %%%------------------------------------------------------------------- 25 | 26 | %%% @doc Parse transform for code generation pseduo functions 27 | %%% 28 | %%%

...

29 | %%% 30 | %%% @end 31 | 32 | -module(parse_trans_codegen). 33 | 34 | -export([parse_transform/2]). 35 | -export([format_error/1]). 36 | 37 | %% @spec (Forms, Options) -> NewForms 38 | %% 39 | %% @doc 40 | %% Searches for calls to pseudo functions in the module `codegen', 41 | %% and converts the corresponding erlang code to a data structure 42 | %% representing the abstract form of that code. 43 | %% 44 | %% The purpose of these functions is to let the programmer write 45 | %% the actual code that is to be generated, rather than manually 46 | %% writing abstract forms, which is more error prone and cannot be 47 | %% checked by the compiler until the generated module is compiled. 48 | %% 49 | %% Supported functions: 50 | %% 51 | %%

gen_function/2

52 | %% 53 | %% Usage: `codegen:gen_function(Name, Fun)' 54 | %% 55 | %% Substitutes the abstract code for a function with name `Name' 56 | %% and the same behaviour as `Fun'. 57 | %% 58 | %% `Fun' can either be a anonymous `fun', which is then converted to 59 | %% a named function, or it can be an `implicit fun', e.g. 60 | %% `fun is_member/2'. In the latter case, the referenced function is fetched 61 | %% and converted to an abstract form representation. It is also renamed 62 | %% so that the generated function has the name `Name'. 63 | %%

64 | %% Another alternative is to wrap a fun inside a list comprehension, e.g. 65 | %%

 66 | %% f(Name, L) ->
 67 | %%     codegen:gen_function(
 68 | %%         Name,
 69 | %%         [ fun({'$var',X}) ->
 70 | %%              {'$var', Y}
 71 | %%           end || {X, Y} &lt;- L ]).
 72 | %% 
73 | %%

74 | %% Calling the above with `f(foo, [{1,a},{2,b},{3,c}])' will result in 75 | %% generated code corresponding to: 76 | %%

 77 | %% foo(1) -> a;
 78 | %% foo(2) -> b;
 79 | %% foo(3) -> c.
 80 | %% 
81 | %% 82 | %%

gen_functions/1

83 | %% 84 | %% Takes a list of `{Name, Fun}' tuples and produces a list of abstract 85 | %% data objects, just as if one had written 86 | %% `[codegen:gen_function(N1,F1),codegen:gen_function(N2,F2),...]'. 87 | %% 88 | %%

exprs/1

89 | %% 90 | %% Usage: `codegen:exprs(Fun)' 91 | %% 92 | %% `Fun' is either an anonymous function, or an implicit fun with only one 93 | %% function clause. This "function" takes the body of the fun and produces 94 | %% a data type representing the abstract form of the list of expressions in 95 | %% the body. The arguments of the function clause are ignored, but can be 96 | %% used to ensure that all necessary variables are known to the compiler. 97 | %% 98 | %%

gen_module/3

99 | %% 100 | %% Generates abstract forms for a complete module definition. 101 | %% 102 | %% Usage: `codegen:gen_module(ModuleName, Exports, Functions)' 103 | %% 104 | %% `ModuleName' is either an atom or a {'$var', V} reference. 105 | %% 106 | %% `Exports' is a list of `{Function, Arity}' tuples. 107 | %% 108 | %% `Functions' is a list of `{Name, Fun}' tuples analogous to that for 109 | %% `gen_functions/1'. 110 | %% 111 | %%

Variable substitution

112 | %% 113 | %% It is possible to do some limited expansion (importing a value 114 | %% bound at compile-time), using the construct {'$var', V}, where 115 | %% `V' is a bound variable in the scope of the call to `gen_function/2'. 116 | %% 117 | %% Example: 118 | %%
119 | %% gen(Name, X) ->
120 | %%    codegen:gen_function(Name, fun(L) -> lists:member({'$var',X}, L) end).
121 | %% 
122 | %% 123 | %% After transformation, calling `gen(contains_17, 17)' will yield the 124 | %% abstract form corresponding to: 125 | %%
126 | %% contains_17(L) ->
127 | %%    lists:member(17, L).
128 | %% 
129 | %% 130 | %%

Form substitution

131 | %% 132 | %% It is possible to inject abstract forms, using the construct 133 | %% {'$form', F}, where `F' is bound to a parsed form in 134 | %% the scope of the call to `gen_function/2'. 135 | %% 136 | %% Example: 137 | %%
138 | %% gen(Name, F) ->
139 | %%    codegen:gen_function(Name, fun(X) -> X =:= {'$form',F} end).
140 | %% 
141 | %% 142 | %% After transformation, calling `gen(is_foo, {atom,0,foo})' will yield the 143 | %% abstract form corresponding to: 144 | %%
145 | %% is_foo(X) ->
146 | %%    X =:= foo.
147 | %% 
148 | %% @end 149 | %% 150 | parse_transform(Forms, Options) -> 151 | Context = parse_trans:initial_context(Forms, Options), 152 | {NewForms, _} = 153 | parse_trans:do_depth_first( 154 | fun xform_fun/4, _Acc = Forms, Forms, Context), 155 | parse_trans:return(parse_trans:revert(NewForms), Context). 156 | 157 | xform_fun(application, Form, _Ctxt, Acc) -> 158 | MFA = erl_syntax_lib:analyze_application(Form), 159 | L = erl_syntax:get_pos(Form), 160 | case MFA of 161 | {codegen, {gen_module, 3}} -> 162 | [NameF, ExportsF, FunsF] = 163 | erl_syntax:application_arguments(Form), 164 | NewForms = gen_module(NameF, ExportsF, FunsF, L, Acc), 165 | {NewForms, Acc}; 166 | {codegen, {gen_function, 2}} -> 167 | [NameF, FunF] = 168 | erl_syntax:application_arguments(Form), 169 | NewForm = gen_function(NameF, FunF, L, L, Acc), 170 | {NewForm, Acc}; 171 | {codegen, {gen_function, 3}} -> 172 | [NameF, FunF, LineF] = 173 | erl_syntax:application_arguments(Form), 174 | NewForm = gen_function( 175 | NameF, FunF, L, erl_syntax:integer_value(LineF), Acc), 176 | {NewForm, Acc}; 177 | {codegen, {gen_function_alt, 3}} -> 178 | [NameF, FunF, AltF] = 179 | erl_syntax:application_arguments(Form), 180 | NewForm = gen_function_alt(NameF, FunF, AltF, L, L, Acc), 181 | {NewForm, Acc}; 182 | {codegen, {gen_functions, 1}} -> 183 | [List] = erl_syntax:application_arguments(Form), 184 | Elems = erl_syntax:list_elements(List), 185 | NewForms = lists:map( 186 | fun(E) -> 187 | [NameF, FunF] = erl_syntax:tuple_elements(E), 188 | gen_function(NameF, FunF, L, L, Acc) 189 | end, Elems), 190 | {erl_syntax:list(NewForms), Acc}; 191 | {codegen, {exprs, 1}} -> 192 | [FunF] = erl_syntax:application_arguments(Form), 193 | [Clause] = erl_syntax:fun_expr_clauses(FunF), 194 | [{clause,_,_,_,Body}] = parse_trans:revert([Clause]), 195 | NewForm = substitute(erl_parse:abstract(Body)), 196 | {NewForm, Acc}; 197 | _ -> 198 | {Form, Acc} 199 | end; 200 | xform_fun(_, Form, _Ctxt, Acc) -> 201 | {Form, Acc}. 202 | 203 | gen_module(NameF, ExportsF, FunsF, L, Acc) -> 204 | case erl_syntax:type(FunsF) of 205 | list -> 206 | try gen_module_(NameF, ExportsF, FunsF, L, Acc) 207 | catch 208 | error:E -> 209 | ErrStr = parse_trans:format_exception(error, E), 210 | {error, {L, ?MODULE, ErrStr}} 211 | end; 212 | _ -> 213 | ErrStr = parse_trans:format_exception( 214 | error, "Argument must be a list"), 215 | {error, {L, ?MODULE, ErrStr}} 216 | end. 217 | 218 | gen_module_(NameF, ExportsF, FunsF, L0, Acc) -> 219 | P = erl_syntax:get_pos(NameF), 220 | ModF = case parse_trans:revert_form(NameF) of 221 | {atom,_,_} = Am -> Am; 222 | {tuple,_,[{atom,_,'$var'}, 223 | {var,_,V}]} -> 224 | {var,P,V} 225 | end, 226 | cons( 227 | {cons,P, 228 | {tuple,P, 229 | [{atom,P,attribute}, 230 | {integer,P,1}, 231 | {atom,P,module}, 232 | ModF]}, 233 | substitute( 234 | abstract( 235 | [{attribute,P,export, 236 | lists:map( 237 | fun(TupleF) -> 238 | [F,A] = erl_syntax:tuple_elements(TupleF), 239 | {erl_syntax:atom_value(F), erl_syntax:integer_value(A)} 240 | end, erl_syntax:list_elements(ExportsF))}]))}, 241 | lists:map( 242 | fun(FTupleF) -> 243 | Pos = erl_syntax:get_pos(FTupleF), 244 | [FName, FFunF] = erl_syntax:tuple_elements(FTupleF), 245 | gen_function(FName, FFunF, L0, Pos, Acc) 246 | end, erl_syntax:list_elements(FunsF))). 247 | 248 | cons({cons,L,H,T}, L2) -> 249 | {cons,L,H,cons(T, L2)}; 250 | cons({nil,L}, [H|T]) -> 251 | Pos = erl_syntax:get_pos(H), 252 | {cons,L,H,cons({nil,Pos}, T)}; 253 | cons({nil,L}, []) -> 254 | {nil,L}. 255 | 256 | 257 | 258 | gen_function(NameF, FunF, L0, L, Acc) -> 259 | try gen_function_(NameF, FunF, [], L, Acc) 260 | catch 261 | error:E -> 262 | ErrStr = parse_trans:format_exception(error, E), 263 | {error, {L0, ?MODULE, ErrStr}} 264 | end. 265 | 266 | gen_function_alt(NameF, FunF, AltF, L0, L, Acc) -> 267 | try gen_function_(NameF, FunF, AltF, L, Acc) 268 | catch 269 | error:E -> 270 | ErrStr = parse_trans:format_exception(error, E), 271 | {error, {L0, ?MODULE, ErrStr}} 272 | end. 273 | 274 | gen_function_(NameF, FunF, AltF, L, Acc) -> 275 | case erl_syntax:type(FunF) of 276 | T when T==implicit_fun; T==fun_expr -> 277 | {Arity, Clauses} = gen_function_clauses(T, NameF, FunF, L, Acc), 278 | {tuple, 1, [{atom, 1, function}, 279 | {integer, 1, L}, 280 | NameF, 281 | {integer, 1, Arity}, 282 | substitute(abstract(Clauses))]}; 283 | list_comp -> 284 | %% Extract the fun from the LC 285 | [Template] = parse_trans:revert( 286 | [erl_syntax:list_comp_template(FunF)]), 287 | %% Process fun in the normal fashion (as above) 288 | {Arity, Clauses} = gen_function_clauses(erl_syntax:type(Template), 289 | NameF, Template, L, Acc), 290 | Body = erl_syntax:list_comp_body(FunF), 291 | %% Collect all variables from the LC generator(s) 292 | %% We want to produce an abstract representation of something like: 293 | %% {function,1,Name,Arity, 294 | %% lists:flatten( 295 | %% [(fun(V1,V2,...) -> 296 | %% ... 297 | %% end)(__V1,__V2,...) || {__V1,__V2,...} <- L])} 298 | %% where the __Vn vars are our renamed versions of the LC generator 299 | %% vars. This allows us to instantiate the clauses at run-time. 300 | Vars = lists:flatten( 301 | [sets:to_list(erl_syntax_lib:variables( 302 | erl_syntax:generator_pattern(G))) 303 | || G <- Body]), 304 | Vars1 = [list_to_atom("__" ++ atom_to_list(V)) || V <- Vars], 305 | VarMap = lists:zip(Vars, Vars1), 306 | Body1 = 307 | [erl_syntax:generator( 308 | rename_vars(VarMap, gen_pattern(G)), 309 | gen_body(G)) || G <- Body], 310 | [RevLC] = parse_trans:revert( 311 | [erl_syntax:list_comp( 312 | {call, 1, 313 | {'fun',1, 314 | {clauses, 315 | [{clause,1,[{var,1,V} || V <- Vars],[], 316 | [substitute( 317 | abstract(Clauses))] 318 | }]} 319 | }, [{var,1,V} || V <- Vars1]}, Body1)]), 320 | AltC = case AltF of 321 | [] -> {nil,1}; 322 | _ -> 323 | {Arity, AltC1} = gen_function_clauses( 324 | erl_syntax:type(AltF), 325 | NameF, AltF, L, Acc), 326 | substitute(abstract(AltC1)) 327 | end, 328 | {tuple,1,[{atom,1,function}, 329 | {integer, 1, L}, 330 | NameF, 331 | {integer, 1, Arity}, 332 | {call, 1, {remote, 1, {atom, 1, lists}, 333 | {atom,1,flatten}}, 334 | [{op, 1, '++', RevLC, AltC}]}]} 335 | end. 336 | 337 | gen_pattern(G) -> 338 | erl_syntax:generator_pattern(G). 339 | 340 | gen_body(G) -> 341 | erl_syntax:generator_body(G). 342 | 343 | rename_vars(Vars, Tree) -> 344 | erl_syntax_lib:map( 345 | fun(T) -> 346 | case erl_syntax:type(T) of 347 | variable -> 348 | V = erl_syntax:variable_name(T), 349 | {_,V1} = lists:keyfind(V,1,Vars), 350 | erl_syntax:variable(V1); 351 | _ -> 352 | T 353 | end 354 | end, Tree). 355 | 356 | gen_function_clauses(implicit_fun, _NameF, FunF, _L, Acc) -> 357 | AQ = erl_syntax:implicit_fun_name(FunF), 358 | Name = erl_syntax:atom_value(erl_syntax:arity_qualifier_body(AQ)), 359 | Arity = erl_syntax:integer_value( 360 | erl_syntax:arity_qualifier_argument(AQ)), 361 | NewForm = find_function(Name, Arity, Acc), 362 | ClauseForms = erl_syntax:function_clauses(NewForm), 363 | {Arity, ClauseForms}; 364 | gen_function_clauses(fun_expr, _NameF, FunF, _L, _Acc) -> 365 | ClauseForms = erl_syntax:fun_expr_clauses(FunF), 366 | Arity = get_arity(ClauseForms), 367 | {Arity, ClauseForms}. 368 | 369 | find_function(Name, Arity, Forms) -> 370 | [Form] = [F || {function,_,N,A,_} = F <- Forms, 371 | N == Name, 372 | A == Arity], 373 | Form. 374 | 375 | abstract(ClauseForms) -> 376 | erl_parse:abstract(parse_trans:revert(ClauseForms)). 377 | 378 | substitute({tuple,L0, 379 | [{atom,_,tuple}, 380 | {integer,_,L}, 381 | {cons,_, 382 | {tuple,_,[{atom,_,atom},{integer,_,_},{atom,_,'$var'}]}, 383 | {cons,_, 384 | {tuple,_,[{atom,_,var},{integer,_,_},{atom,_,V}]}, 385 | {nil,_}}}]}) -> 386 | {call, L0, {remote,L0,{atom,L0,erl_parse}, 387 | {atom,L0,abstract}}, 388 | [{var, L0, V}, {integer, L0, L}]}; 389 | substitute({tuple,L0, 390 | [{atom,_,tuple}, 391 | {integer,_,_}, 392 | {cons,_, 393 | {tuple,_,[{atom,_,atom},{integer,_,_},{atom,_,'$form'}]}, 394 | {cons,_, 395 | {tuple,_,[{atom,_,var},{integer,_,_},{atom,_,F}]}, 396 | {nil,_}}}]}) -> 397 | {var, L0, F}; 398 | substitute([]) -> 399 | []; 400 | substitute([H|T]) -> 401 | [substitute(H) | substitute(T)]; 402 | substitute(T) when is_tuple(T) -> 403 | list_to_tuple(substitute(tuple_to_list(T))); 404 | substitute(X) -> 405 | X. 406 | 407 | get_arity(Clauses) -> 408 | Ays = [length(erl_syntax:clause_patterns(C)) || C <- Clauses], 409 | case lists:usort(Ays) of 410 | [Ay] -> 411 | Ay; 412 | Other -> 413 | erlang:error(ambiguous, Other) 414 | end. 415 | 416 | 417 | format_error(E) -> 418 | case io_lib:deep_char_list(E) of 419 | true -> 420 | E; 421 | _ -> 422 | io_lib:write(E) 423 | end. 424 | -------------------------------------------------------------------------------- /doc/parse_trans.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | #Module parse_trans# 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | 10 | Generic parse transform library for Erlang. 11 | 12 | 13 | 14 | __Authors:__ : Ulf Wiger ([`ulf.wiger@feuerlabs.com`](mailto:ulf.wiger@feuerlabs.com)). 15 | 16 | ##Description## 17 | 18 | 19 | ... 20 | 21 | 22 | 23 | ##Data Types## 24 | 25 | 26 | 27 | 28 | ###form()## 29 | 30 | 31 | 32 |
form() = any()
33 | 34 | 35 | 36 | ###forms()## 37 | 38 | 39 | 40 |
forms() = [form()]
41 | 42 | 43 | 44 | ###insp_f()## 45 | 46 | 47 | 48 |
insp_f() = fun((type(), form(), #context{}, A) -> {boolean(), A})
49 | 50 | 51 | 52 | ###options()## 53 | 54 | 55 | 56 |
options() = [{atom(), any()}]
57 | 58 | 59 | 60 | ###type()## 61 | 62 | 63 | 64 |
type() = atom()
65 | 66 | 67 | 68 | ###xform_f_df()## 69 | 70 | 71 | 72 |
xform_f_df() = fun((type(), form(), #context{}, Acc) -> {form(), Acc} | {forms(), form(), forms(), Acc})
73 | 74 | 75 | 76 | ###xform_f_rec()## 77 | 78 | 79 | 80 |
xform_f_rec() = fun((type(), form(), #context{}, Acc) -> {form(), boolean(), Acc} | {forms(), form(), forms(), boolean(), Acc})
81 | 82 | 83 | ##Function Index## 84 | 85 | 86 |
context/2 87 | Accessor function for the Context record.
depth_first/4
do_depth_first/4
do_insert_forms/4
do_inspect/4
do_transform/4
error/3.
export_function/3
format_error/1
format_exception/2Equivalent to format_exception(Class, Reason, 4).
format_exception/3Produces a few lines of user-friendly formatting of exception info.
function_exists/3 88 | Checks whether the given function is defined in Forms.
get_attribute/2 89 | Returns the value of the first occurence of attribute A.
get_attribute/3
get_file/1 90 | Returns the name of the file being compiled.
get_module/1 91 | Returns the name of the module being compiled.
get_orig_syntax_tree/1.
get_pos/1 92 | Tries to retrieve the line number from an erl_syntax form.
initial_context/2 93 | Initializes a context record.
inspect/4 94 | Equvalent to do_inspect(Fun,Acc,Forms,initial_context(Forms,Options)).
optionally_pretty_print/3
plain_transform/2 95 | Performs a transform of Forms using the fun Fun(Form).
pp_beam/1 96 | Reads debug_info from the beam file Beam and returns a string containing 97 | the pretty-printed corresponding erlang source code.
pp_beam/2 98 | Reads debug_info from the beam file Beam and pretty-prints it as 99 | Erlang source code, storing it in the file Out.
pp_src/2Pretty-prints the erlang source code corresponding to Forms into Out.
replace_function/4
return/2Checks the transformed result for errors and warnings.
revert/1Reverts back from Syntax Tools format to Erlang forms.
revert_form/1Reverts a single form back from Syntax Tools format to Erlang forms.
top/3
transform/4 100 | Makes one pass.
101 | 102 | 103 | 104 | 105 | ##Function Details## 106 | 107 | 108 | 109 | ###context/2## 110 | 111 | 112 | 113 | 114 |
context(X1::Attr, Context) -> any()
115 |
  • Attr = module | function | arity | options
116 | 117 | 118 | 119 | 120 | Accessor function for the Context record. 121 | 122 | ###depth_first/4## 123 | 124 | 125 | 126 | 127 |
depth_first(Fun::xform_f_df(), Acc, Forms::forms(), Options::options()) -> {forms(), Acc} | {error, list()}
128 |

129 | 130 | 131 | 132 | 133 | ###do_depth_first/4## 134 | 135 | 136 | 137 | 138 |
do_depth_first(F::xform_f_df(), Acc::term(), Forms::forms(), Context::#context{}) -> {forms(), term()}
139 |

140 | 141 | 142 | 143 | 144 | ###do_insert_forms/4## 145 | 146 | 147 | 148 | 149 |
do_insert_forms(X1::above | below, Insert::forms(), Forms::forms(), Context::#context{}) -> forms()
150 |

151 | 152 | 153 | 154 | 155 | ###do_inspect/4## 156 | 157 | 158 | 159 | 160 |
do_inspect(F::insp_f(), Acc::term(), Forms::forms(), Context::#context{}) -> term()
161 |

162 | 163 | 164 | 165 | 166 | ###do_transform/4## 167 | 168 | 169 | 170 | 171 |
do_transform(F::xform_f_rec(), Acc::term(), Forms::forms(), Context::#context{}) -> {forms(), term()}
172 |

173 | 174 | 175 | 176 | 177 | ###error/3## 178 | 179 | 180 | 181 | 182 |
error(R::Reason, F::Form, I::Info) -> throw()
183 |
  • Info = [{Key, Value}]
184 | 185 | 186 | 187 | 188 | 189 | 190 | Used to report errors detected during the parse transform. 191 | 192 | ###export_function/3## 193 | 194 | 195 | 196 | 197 | `export_function(F, Arity, Forms) -> any()` 198 | 199 | 200 | 201 | ###format_error/1## 202 | 203 | 204 | 205 | 206 |
format_error(Error::{atom(), term()}) -> iolist()
207 |

208 | 209 | 210 | 211 | 212 | ###format_exception/2## 213 | 214 | 215 | 216 | 217 |
format_exception(Class, Reason) -> String
218 |

219 | 220 | 221 | 222 | 223 | Equivalent to [`format_exception(Class, Reason, 4)`](#format_exception-3). 224 | 225 | ###format_exception/3## 226 | 227 | 228 | 229 | 230 |
format_exception(Class, Reason, Lines) -> String
231 |
  • Class = error | throw | exit
  • Reason = term()
  • Lines = integer() | infinity
232 | 233 | 234 | 235 | 236 | 237 | Produces a few lines of user-friendly formatting of exception info 238 | 239 | This function is very similar to the exception pretty-printing in the shell, 240 | but returns a string that can be used as error info e.g. by error forms 241 | handled by [`return/2`](#return-2). By default, the first 4 lines of the 242 | pretty-printed exception info are returned, but this can be controlled 243 | with the `Lines` parameter.Note that a stacktrace is generated inside this function. 244 | 245 | ###function_exists/3## 246 | 247 | 248 | 249 | 250 |
function_exists(Fname::atom(), Arity::integer(), Forms) -> boolean()
251 |

252 | 253 | 254 | 255 | 256 | 257 | Checks whether the given function is defined in Forms. 258 | 259 | ###get_attribute/2## 260 | 261 | 262 | 263 | 264 |
get_attribute(A, Forms) -> any()
265 |
  • A = atom()
266 | 267 | 268 | 269 | 270 | Returns the value of the first occurence of attribute A. 271 | 272 | ###get_attribute/3## 273 | 274 | 275 | 276 | 277 | `get_attribute(A, Forms, Undef) -> any()` 278 | 279 | 280 | 281 | ###get_file/1## 282 | 283 | 284 | 285 | 286 |
get_file(Forms) -> string()
287 |

288 | 289 | 290 | 291 | 292 | 293 | Returns the name of the file being compiled. 294 | 295 | ###get_module/1## 296 | 297 | 298 | 299 | 300 |
get_module(Forms) -> atom()
301 |

302 | 303 | 304 | 305 | 306 | 307 | Returns the name of the module being compiled. 308 | 309 | ###get_orig_syntax_tree/1## 310 | 311 | 312 | 313 | 314 |
get_orig_syntax_tree(File) -> Forms
315 |

316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | Fetches a Syntax Tree representing the code before pre-processing, 324 | that is, including record and macro definitions. Note that macro 325 | definitions must be syntactically complete forms (this function 326 | uses epp_dodger). 327 | 328 | ###get_pos/1## 329 | 330 | 331 | 332 | 333 |
get_pos(I::list()) -> integer()
334 |

335 | 336 | 337 | 338 | 339 | 340 | Tries to retrieve the line number from an erl_syntax form. Returns a 341 | (very high) dummy number if not successful. 342 | 343 | ###initial_context/2## 344 | 345 | 346 | 347 | 348 |
initial_context(Forms, Options) -> #context{}
349 |

350 | 351 | 352 | 353 | 354 | 355 | Initializes a context record. When traversing through the form 356 | list, the context is updated to reflect the current function and 357 | arity. Static elements in the context are the file name, the module 358 | name and the options passed to the transform function. 359 | 360 | ###inspect/4## 361 | 362 | 363 | 364 | 365 |
inspect(F::Fun, Acc::Forms, Forms::Acc, Options) -> NewAcc
366 |
  • Fun = function()
367 | 368 | 369 | 370 | 371 | Equvalent to do_inspect(Fun,Acc,Forms,initial_context(Forms,Options)). 372 | 373 | ###optionally_pretty_print/3## 374 | 375 | 376 | 377 | 378 |
optionally_pretty_print(Result::forms(), Options::options(), Context::#context{}) -> ok
379 |

380 | 381 | 382 | 383 | 384 | ###plain_transform/2## 385 | 386 | 387 | 388 | 389 |
plain_transform(Fun, Forms) -> forms()
390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | Performs a transform of `Forms` using the fun `Fun(Form)`. `Form` is always 398 | an Erlang abstract form, i.e. it is not converted to syntax_tools 399 | representation. The intention of this transform is for the fun to have a 400 | catch-all clause returning `continue`. This will ensure that it stays robust 401 | against additions to the language. 402 | 403 | `Fun(Form)` must return either of the following: 404 | 405 | * `NewForm` - any valid form 406 | * `continue` - dig into the sub-expressions of the form 407 | * `{done, NewForm}` - Replace `Form` with `NewForm`; return all following 408 | forms unchanged 409 | * `{error, Reason}` - Abort transformation with an error message.Example - This transform fun would convert all instances of `P ! Msg` to 410 | `gproc:send(P, Msg)`: 411 |
412 |   parse_transform(Forms, _Options) ->
413 |       parse_trans:plain_transform(fun do_transform/1, Forms).
414 |   do_transform({'op', L, '!', Lhs, Rhs}) ->
415 |        [NewLhs] = parse_trans:plain_transform(fun do_transform/1, [Lhs]),
416 |        [NewRhs] = parse_trans:plain_transform(fun do_transform/1, [Rhs]),
417 |       {call, L, {remote, L, {atom, L, gproc}, {atom, L, send}},
418 |        [NewLhs, NewRhs]};
419 |   do_transform(_) ->
420 |       continue.
421 | 422 | ###pp_beam/1## 423 | 424 | 425 | 426 | 427 |
pp_beam(Beam::file:filename()) -> string() | {error, Reason}
428 |

429 | 430 | 431 | 432 | 433 | 434 | Reads debug_info from the beam file Beam and returns a string containing 435 | the pretty-printed corresponding erlang source code. 436 | 437 | ###pp_beam/2## 438 | 439 | 440 | 441 | 442 |
pp_beam(Beam::filename(), Out::filename()) -> ok | {error, Reason}
443 |

444 | 445 | 446 | 447 | 448 | 449 | Reads debug_info from the beam file Beam and pretty-prints it as 450 | Erlang source code, storing it in the file Out. 451 | 452 | ###pp_src/2## 453 | 454 | 455 | 456 | 457 |
pp_src(Res::Forms, Out::filename()) -> ok
458 |

459 | 460 | 461 | 462 | 463 | Pretty-prints the erlang source code corresponding to Forms into Out 464 | 465 | 466 | ###replace_function/4## 467 | 468 | 469 | 470 | 471 | `replace_function(F, Arity, NewForm, Forms) -> any()` 472 | 473 | 474 | 475 | ###return/2## 476 | 477 | 478 | 479 | 480 |
return(Forms, Context) -> Forms | {error, Es, Ws} | {warnings, Forms, Ws}
481 |

482 | 483 | 484 | 485 | 486 | Checks the transformed result for errors and warnings 487 | 488 | 489 | Errors and warnings can be produced from inside a parse transform, with 490 | a bit of care. The easiest way is to simply produce an `{error, Err}` or 491 | `{warning, Warn}` form in place. This function finds such forms, and 492 | removes them from the form list (otherwise, the linter will crash), and 493 | produces a return value that the compiler can work with.The format of the `error` and `warning` "forms" must be 494 | `{Tag, {Pos, Module, Info}}`, where: 495 | 496 | * `Tag :: error | warning` 497 | 498 | * `Pos :: LineNumber | {LineNumber, ColumnNumber}` 499 | 500 | * `Module` is a module that exports a corresponding 501 | `Module:format_error(Info)` 502 | 503 | * `Info :: term()` 504 | 505 | 506 | 507 | 508 | If the error is in the form of a caught exception, `Info` may be produced 509 | using the function [`format_exception/2`](#format_exception-2). 510 | 511 | ###revert/1## 512 | 513 | 514 | 515 | 516 |
revert(Tree) -> Forms
517 |

518 | 519 | 520 | 521 | 522 | Reverts back from Syntax Tools format to Erlang forms. 523 | 524 | 525 | Note that the Erlang forms are a subset of the Syntax Tools 526 | syntax tree, so this function is safe to call even on a list of 527 | regular Erlang forms. 528 | 529 | 530 | Note2: R16B03 introduced a bug, where forms produced by 531 | `erl_syntax:revert/1` (specifically, implicit funs) could crash the linter. 532 | This function works around that limitation, after first verifying that it's 533 | necessary to do so. Use of the workaround can be forced with the help of 534 | the `parse_trans` environment variable {revert_workaround, true}. This 535 | variable will be removed when R16B03 is no longer 'supported'. 536 | 537 | ###revert_form/1## 538 | 539 | 540 | 541 | 542 |
revert_form(F::Tree) -> Form
543 |

544 | 545 | 546 | 547 | 548 | Reverts a single form back from Syntax Tools format to Erlang forms. 549 | 550 | 551 | `erl_syntax:revert/1` has had a long-standing bug where it doesn't 552 | completely revert attribute forms. This function deals properly with those 553 | cases. 554 | 555 | 556 | Note that the Erlang forms are a subset of the Syntax Tools 557 | syntax tree, so this function is safe to call even on a regular Erlang 558 | form. 559 | 560 | 561 | Note2: R16B03 introduced a bug, where forms produced by 562 | `erl_syntax:revert/1` (specifically, implicit funs) could crash the linter. 563 | This function works around that limitation, after first verifying that it's 564 | necessary to do so. Use of the workaround can be forced with the help of 565 | the `parse_trans` environment variable {revert_workaround, true}. This 566 | variable will be removed when R16B03 is no longer 'supported'. 567 | 568 | ###top/3## 569 | 570 | 571 | 572 | 573 |
top(F::function(), Forms::forms(), Options::list()) -> forms() | {error, term()}
574 |

575 | 576 | 577 | 578 | 579 | ###transform/4## 580 | 581 | 582 | 583 | 584 |
transform(Fun, Acc, Forms, Options) -> {TransformedForms, NewAcc}
585 |
  • Fun = function()
  • Options = [{Key, Value}]
586 | 587 | 588 | 589 | 590 | Makes one pass -------------------------------------------------------------------------------- /src/parse_trans.erl: -------------------------------------------------------------------------------- 1 | %%% The contents of this file are subject to the Erlang Public License, 2 | %%% Version 1.1, (the "License"); you may not use this file except in 3 | %%% compliance with the License. You may obtain a copy of the License at 4 | %%% http://www.erlang.org/EPLICENSE 5 | %%% 6 | %%% Software distributed under the License is distributed on an "AS IS" 7 | %%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 8 | %%% the License for the specific language governing rights and limitations 9 | %%% under the License. 10 | %%% 11 | %%% The Original Code is exprecs-0.2. 12 | %%% 13 | %%% The Initial Developer of the Original Code is Ericsson AB. 14 | %%% Portions created by Ericsson are Copyright (C), 2006, Ericsson AB. 15 | %%% All Rights Reserved. 16 | %%% 17 | %%% Contributor(s): ______________________________________. 18 | 19 | %%%------------------------------------------------------------------- 20 | %%% File : parse_trans.erl 21 | %%% @author : Ulf Wiger 22 | %%% @end 23 | %%% Description : 24 | %%% 25 | %%% Created : 13 Feb 2006 by Ulf Wiger (then Ericsson) 26 | %%%------------------------------------------------------------------- 27 | 28 | %%% @doc Generic parse transform library for Erlang. 29 | %%% 30 | %%%

...

31 | %%% 32 | %%% @end 33 | 34 | -module(parse_trans). 35 | 36 | -export([plain_transform/2]). 37 | 38 | -export([ 39 | inspect/4, 40 | transform/4, 41 | depth_first/4, 42 | revert/1, 43 | revert_form/1, 44 | format_exception/2, format_exception/3, 45 | return/2 46 | ]). 47 | 48 | -export([ 49 | error/3, 50 | format_error/1 51 | ]). 52 | 53 | -export([ 54 | initial_context/2, 55 | do_inspect/4, 56 | do_transform/4, 57 | do_depth_first/4, 58 | top/3 59 | ]). 60 | 61 | -export([do_insert_forms/4, 62 | replace_function/4, 63 | export_function/3]). 64 | 65 | -export([ 66 | context/2, 67 | get_pos/1, 68 | get_file/1, 69 | get_module/1, 70 | get_attribute/2, 71 | get_attribute/3, 72 | get_orig_syntax_tree/1, 73 | function_exists/3, 74 | optionally_pretty_print/3, 75 | pp_src/2, 76 | pp_beam/1, pp_beam/2 77 | ]). 78 | 79 | -import(erl_syntax, [atom_value/1, 80 | attribute_name/1, 81 | attribute_arguments/1, 82 | string_value/1, 83 | type/1 84 | ]). 85 | 86 | -record(context, {module, 87 | function, 88 | arity, 89 | file, 90 | options}). 91 | 92 | %% Useful macros for debugging and error reporting 93 | -define(HERE, {?MODULE, ?LINE}). 94 | 95 | -define(DUMMY_LINE, 9999). 96 | 97 | -define(ERROR(R, F, I), 98 | begin 99 | Trace = erlang:get_stacktrace(), 100 | rpt_error(R, F, I, Trace), 101 | throw({error,get_pos(I),{R, Trace}}) 102 | end). 103 | 104 | -export_type([forms/0]). 105 | 106 | %% Typedefs 107 | -type form() :: any(). 108 | -type forms() :: [form()]. 109 | -type options() :: [{atom(), any()}]. 110 | -type type() :: atom(). 111 | -type xform_f_rec() :: fun((type(), form(), #context{}, Acc) -> 112 | {form(), boolean(), Acc} 113 | | {forms(), form(), forms(), boolean(), Acc}). 114 | -type xform_f_df() :: fun((type(), form(), #context{}, Acc) -> 115 | {form(), Acc} 116 | | {forms(), form(), forms(), Acc}). 117 | -type insp_f() :: fun((type(), form(), #context{}, A) -> {boolean(), A}). 118 | 119 | 120 | %%% @spec (Reason, Form, Info) -> throw() 121 | %%% Info = [{Key,Value}] 122 | %%% 123 | %%% @doc 124 | %%%

Used to report errors detected during the parse transform.

125 | %%% @end 126 | %%% 127 | -spec error(string(), any(), [{any(),any()}]) -> 128 | none(). 129 | error(R, _F, I) -> 130 | % rpt_error(R, F, I, erlang:get_stacktrace()), 131 | throw({error,get_pos(I),{R, erlang:get_stacktrace()}}). 132 | 133 | %% @spec plain_transform(Fun, Forms) -> forms() 134 | %% Fun = function() 135 | %% Forms = forms() 136 | %% 137 | %% @doc 138 | %% Performs a transform of `Forms' using the fun `Fun(Form)'. `Form' is always 139 | %% an Erlang abstract form, i.e. it is not converted to syntax_tools 140 | %% representation. The intention of this transform is for the fun to have a 141 | %% catch-all clause returning `continue'. This will ensure that it stays robust 142 | %% against additions to the language. 143 | %% 144 | %% `Fun(Form)' must return either of the following: 145 | %% 146 | %% * `NewForm' - any valid form 147 | %% * `continue' - dig into the sub-expressions of the form 148 | %% * `{done, NewForm}' - Replace `Form' with `NewForm'; return all following 149 | %% forms unchanged 150 | %% * `{error, Reason}' - Abort transformation with an error message. 151 | %% 152 | %% Example - This transform fun would convert all instances of `P ! Msg' to 153 | %% `gproc:send(P, Msg)': 154 | %%
155 | %% parse_transform(Forms, _Options) ->
156 | %%     parse_trans:plain_transform(fun do_transform/1, Forms).
157 | %%
158 | %% do_transform({'op', L, '!', Lhs, Rhs}) ->
159 | %%      [NewLhs] = parse_trans:plain_transform(fun do_transform/1, [Lhs]),
160 | %%      [NewRhs] = parse_trans:plain_transform(fun do_transform/1, [Rhs]),
161 | %%     {call, L, {remote, L, {atom, L, gproc}, {atom, L, send}},
162 | %%      [NewLhs, NewRhs]};
163 | %% do_transform(_) ->
164 | %%     continue.
165 | %% 
166 | %% @end 167 | %% 168 | plain_transform(Fun, Forms) when is_function(Fun, 1), is_list(Forms) -> 169 | plain_transform1(Fun, Forms). 170 | 171 | plain_transform1(_, []) -> 172 | []; 173 | plain_transform1(Fun, [F|Fs]) when is_atom(element(1,F)) -> 174 | case Fun(F) of 175 | skip -> 176 | plain_transform1(Fun, Fs); 177 | continue -> 178 | [list_to_tuple(plain_transform1(Fun, tuple_to_list(F))) | 179 | plain_transform1(Fun, Fs)]; 180 | {done, NewF} -> 181 | [NewF | Fs]; 182 | {error, Reason} -> 183 | error(Reason, F, [{form, F}]); 184 | NewF when is_tuple(NewF) -> 185 | [NewF | plain_transform1(Fun, Fs)] 186 | end; 187 | plain_transform1(Fun, [L|Fs]) when is_list(L) -> 188 | [plain_transform1(Fun, L) | plain_transform1(Fun, Fs)]; 189 | plain_transform1(Fun, [F|Fs]) -> 190 | [F | plain_transform1(Fun, Fs)]; 191 | plain_transform1(_, F) -> 192 | F. 193 | 194 | 195 | %% @spec (list()) -> integer() 196 | %% 197 | %% @doc 198 | %% Tries to retrieve the line number from an erl_syntax form. Returns a 199 | %% (very high) dummy number if not successful. 200 | %% @end 201 | %% 202 | -spec get_pos(list()) -> 203 | integer(). 204 | get_pos(I) when is_list(I) -> 205 | case proplists:get_value(form, I) of 206 | undefined -> 207 | ?DUMMY_LINE; 208 | Form -> 209 | erl_syntax:get_pos(Form) 210 | end. 211 | 212 | 213 | %%% @spec (Forms) -> string() 214 | %%% @doc 215 | %%% Returns the name of the file being compiled. 216 | %%% @end 217 | %%% 218 | -spec get_file(forms()) -> 219 | string(). 220 | get_file(Forms) -> 221 | string_value(hd(get_attribute(file, Forms, [erl_syntax:string("undefined")]))). 222 | 223 | 224 | 225 | %%% @spec (Forms) -> atom() 226 | %%% @doc 227 | %%% Returns the name of the module being compiled. 228 | %%% @end 229 | %%% 230 | -spec get_module([any()]) -> 231 | atom(). 232 | get_module(Forms) -> 233 | atom_value(hd(get_attribute(module, Forms, [erl_syntax:atom(undefined)]))). 234 | 235 | 236 | 237 | %%% @spec (A, Forms) -> any() 238 | %%% A = atom() 239 | %%% 240 | %%% @doc 241 | %%% Returns the value of the first occurence of attribute A. 242 | %%% @end 243 | %%% 244 | -spec get_attribute(atom(), [any()]) -> 245 | 'none' | [erl_syntax:syntaxTree()]. 246 | %% 247 | get_attribute(A, Forms) -> get_attribute(A,Forms,[erl_syntax:atom(undefined)]). 248 | get_attribute(A, Forms, Undef) -> 249 | case find_attribute(A, Forms) of 250 | false -> 251 | Undef; 252 | Other -> 253 | Other 254 | end. 255 | 256 | find_attribute(A, [F|Forms]) -> 257 | case type(F) == attribute 258 | andalso atom_value(attribute_name(F)) == A of 259 | true -> 260 | attribute_arguments(F); 261 | false -> 262 | find_attribute(A, Forms) 263 | end; 264 | find_attribute(_, []) -> 265 | false. 266 | 267 | %% @spec (Fname::atom(), Arity::integer(), Forms) -> boolean() 268 | %% 269 | %% @doc 270 | %% Checks whether the given function is defined in Forms. 271 | %% @end 272 | %% 273 | -spec function_exists(atom(), integer(), forms()) -> 274 | boolean(). 275 | function_exists(Fname, Arity, Forms) -> 276 | Fns = proplists:get_value( 277 | functions, erl_syntax_lib:analyze_forms(Forms), []), 278 | lists:member({Fname,Arity}, Fns). 279 | 280 | 281 | %%% @spec (Forms, Options) -> #context{} 282 | %%% 283 | %%% @doc 284 | %%% Initializes a context record. When traversing through the form 285 | %%% list, the context is updated to reflect the current function and 286 | %%% arity. Static elements in the context are the file name, the module 287 | %%% name and the options passed to the transform function. 288 | %%% @end 289 | %%% 290 | -spec initial_context(forms(), options()) -> 291 | #context{}. 292 | initial_context(Forms, Options) -> 293 | File = get_file(Forms), 294 | Module = get_module(Forms), 295 | #context{file = File, 296 | module = Module, 297 | options = Options}. 298 | 299 | %%% @spec (Fun, Acc, Forms, Options) -> {TransformedForms, NewAcc} 300 | %%% Fun = function() 301 | %%% Options = [{Key,Value}] 302 | %%% 303 | %%% @doc 304 | %%% Makes one pass 305 | %%% @end 306 | -spec transform(xform_f_rec(), Acc, forms(), options()) -> 307 | {forms(), Acc} | {error, list()}. 308 | transform(Fun, Acc, Forms, Options) when is_function(Fun, 4) -> 309 | do(fun do_transform/4, Fun, Acc, Forms, Options). 310 | 311 | -spec depth_first(xform_f_df(), Acc, forms(), options()) -> 312 | {forms(), Acc} | {error, list()}. 313 | depth_first(Fun, Acc, Forms, Options) when is_function(Fun, 4) -> 314 | do(fun do_depth_first/4, Fun, Acc, Forms, Options). 315 | 316 | do(Transform, Fun, Acc, Forms, Options) -> 317 | Context = initial_context(Forms, Options), 318 | File = Context#context.file, 319 | try Transform(Fun, Acc, Forms, Context) of 320 | {NewForms, Acc1} when is_list(NewForms) -> 321 | NewForms1 = optionally_renumber(NewForms, Options), 322 | optionally_pretty_print(NewForms1, Options, Context), 323 | {NewForms1, Acc1} 324 | catch 325 | error:Reason -> 326 | {error, 327 | [{File, [{?DUMMY_LINE, ?MODULE, 328 | {Reason, erlang:get_stacktrace()}}]}]}; 329 | throw:{error, Ln, What} -> 330 | {error, [{error, {Ln, ?MODULE, What}}]} 331 | end. 332 | 333 | -spec top(function(), forms(), list()) -> 334 | forms() | {error, term()}. 335 | top(F, Forms, Options) -> 336 | Context = initial_context(Forms, Options), 337 | File = Context#context.file, 338 | try F(Forms, Context) of 339 | {error, Reason} -> {error, Reason}; 340 | NewForms when is_list(NewForms) -> 341 | NewForms1 = optionally_renumber(NewForms, Options), 342 | optionally_pretty_print(NewForms1, Options, Context), 343 | NewForms1 344 | catch 345 | error:Reason -> 346 | {error, 347 | [{File, [{?DUMMY_LINE, ?MODULE, 348 | {Reason, erlang:get_stacktrace()}}]}]}; 349 | throw:{error, Ln, What} -> 350 | {error, [{File, [{Ln, ?MODULE, What}]}], []} 351 | end. 352 | 353 | replace_function(F, Arity, NewForm, Forms) -> 354 | {NewForms, _} = 355 | do_transform( 356 | fun(function, Form, _Ctxt, Acc) -> 357 | case erl_syntax:revert(Form) of 358 | {function, _, F, Arity, _} -> 359 | {NewForm, false, Acc}; 360 | _ -> 361 | {Form, false, Acc} 362 | end; 363 | (_, Form, _Ctxt, Acc) -> 364 | {Form, false, Acc} 365 | end, false, Forms, initial_context(Forms, [])), 366 | revert(NewForms). 367 | 368 | export_function(F, Arity, Forms) -> 369 | do_insert_forms(above, [{attribute, 1, export, [{F, Arity}]}], Forms, 370 | initial_context(Forms, [])). 371 | 372 | -spec do_insert_forms(above | below, forms(), forms(), #context{}) -> 373 | forms(). 374 | do_insert_forms(above, Insert, Forms, Context) when is_list(Insert) -> 375 | {NewForms, _} = 376 | do_transform( 377 | fun(function, F, _Ctxt, false) -> 378 | {Insert, F, [], _Recurse = false, true}; 379 | (_, F, _Ctxt, Acc) -> 380 | {F, _Recurse = false, Acc} 381 | end, false, Forms, Context), 382 | NewForms; 383 | do_insert_forms(below, Insert, Forms, _Context) when is_list(Insert) -> 384 | insert_below(Forms, Insert). 385 | 386 | 387 | insert_below([F|Rest], Insert) -> 388 | case type(F) of 389 | eof_marker -> 390 | Insert ++ [F]; 391 | _ -> 392 | [F|insert_below(Rest, Insert)] 393 | end. 394 | 395 | -spec optionally_pretty_print(forms(), options(), #context{}) -> 396 | ok. 397 | optionally_pretty_print(Result, Options, Context) -> 398 | DoPP = option_value(pt_pp_src, Options, Result), 399 | DoLFs = option_value(pt_log_forms, Options, Result), 400 | File = Context#context.file, 401 | if DoLFs -> 402 | Out1 = outfile(File, forms), 403 | {ok,Fd} = file:open(Out1, [write]), 404 | try lists:foreach(fun(F) -> io:fwrite(Fd, "~p.~n", [F]) end, Result) 405 | after 406 | ok = file:close(Fd) 407 | end; 408 | true -> ok 409 | end, 410 | if DoPP -> 411 | Out2 = outfile(File, pp), 412 | pp_src(Result, Out2), 413 | io:fwrite("Pretty-printed in ~p~n", [Out2]); 414 | true -> ok 415 | end. 416 | 417 | optionally_renumber(Result, Options) -> 418 | case option_value(pt_renumber, Options, Result) of 419 | true -> 420 | io:fwrite("renumbering...~n", []), 421 | Rev = revert(Result), 422 | renumber_(Rev); 423 | false -> 424 | Result 425 | end. 426 | 427 | renumber_(L) when is_list(L) -> 428 | {Result, _} = renumber_(L, 1), 429 | Result. 430 | 431 | renumber_(L, Acc) when is_list(L) -> 432 | lists:mapfoldl(fun renumber_/2, Acc, L); 433 | renumber_(T, Prev) when is_tuple(T) -> 434 | case is_form(T) of 435 | true -> 436 | New = Prev+1, 437 | T1 = setelement(2, T, New), 438 | {Res, NewAcc} = renumber_(tuple_to_list(T1), New), 439 | {list_to_tuple(Res), NewAcc}; 440 | false -> 441 | L = tuple_to_list(T), 442 | {Res, NewAcc} = renumber_(L, Prev), 443 | {list_to_tuple(Res), NewAcc} 444 | end; 445 | renumber_(X, Prev) -> 446 | {X, Prev}. 447 | 448 | is_form(T) when element(1,T)==type -> true; 449 | is_form(T) -> 450 | try erl_syntax:type(T), 451 | true 452 | catch 453 | error:_ -> 454 | false 455 | end. 456 | 457 | option_value(Key, Options, Result) -> 458 | case proplists:get_value(Key, Options) of 459 | undefined -> 460 | case find_attribute(Key,Result) of 461 | [Expr] -> 462 | type(Expr) == atom andalso 463 | atom_value(Expr) == true; 464 | _ -> 465 | false 466 | end; 467 | V when is_boolean(V) -> 468 | V 469 | end. 470 | 471 | 472 | %%% @spec (Fun, Forms, Acc, Options) -> NewAcc 473 | %%% Fun = function() 474 | %%% @doc 475 | %%% Equvalent to do_inspect(Fun,Acc,Forms,initial_context(Forms,Options)). 476 | %%% @end 477 | %%% 478 | -spec inspect(insp_f(), A, forms(), options()) -> 479 | A. 480 | inspect(F, Acc, Forms, Options) -> 481 | Context = initial_context(Forms, Options), 482 | do_inspect(F, Acc, Forms, Context). 483 | 484 | 485 | 486 | outfile(File, Type) -> 487 | "lre." ++ RevF = lists:reverse(File), 488 | lists:reverse(RevF) ++ ext(Type). 489 | 490 | ext(pp) -> ".xfm"; 491 | ext(forms) -> ".xforms". 492 | 493 | %% @spec (Forms, Out::filename()) -> ok 494 | %% 495 | %% @doc Pretty-prints the erlang source code corresponding to Forms into Out 496 | %% 497 | -spec pp_src(forms(), string()) -> 498 | ok. 499 | pp_src(Res, F) -> 500 | parse_trans_pp:pp_src(Res, F). 501 | %% Str = [io_lib:fwrite("~s~n", 502 | %% [lists:flatten([erl_pp:form(Fm) || 503 | %% Fm <- revert(Res)])])], 504 | %% file:write_file(F, list_to_binary(Str)). 505 | 506 | %% @spec (Beam::file:filename()) -> string() | {error, Reason} 507 | %% 508 | %% @doc 509 | %% Reads debug_info from the beam file Beam and returns a string containing 510 | %% the pretty-printed corresponding erlang source code. 511 | %% @end 512 | -spec pp_beam(file:filename()) -> ok. 513 | pp_beam(Beam) -> 514 | parse_trans_pp:pp_beam(Beam). 515 | 516 | %% @spec (Beam::filename(), Out::filename()) -> ok | {error, Reason} 517 | %% 518 | %% @doc 519 | %% Reads debug_info from the beam file Beam and pretty-prints it as 520 | %% Erlang source code, storing it in the file Out. 521 | %% @end 522 | %% 523 | -spec pp_beam(file:filename(), file:filename()) -> ok. 524 | pp_beam(F, Out) -> 525 | parse_trans_pp:pp_beam(F, Out). 526 | 527 | 528 | %%% @spec (File) -> Forms 529 | %%% 530 | %%% @doc 531 | %%%

Fetches a Syntax Tree representing the code before pre-processing, 532 | %%% that is, including record and macro definitions. Note that macro 533 | %%% definitions must be syntactically complete forms (this function 534 | %%% uses epp_dodger).

535 | %%% @end 536 | %%% 537 | -spec get_orig_syntax_tree(string()) -> 538 | forms(). 539 | get_orig_syntax_tree(File) -> 540 | case epp_dodger:parse_file(File) of 541 | {ok, Forms} -> 542 | Forms; 543 | Err -> 544 | error(error_reading_file, ?HERE, [{File,Err}]) 545 | end. 546 | 547 | %%% @spec (Tree) -> Forms 548 | %%% 549 | %%% @doc Reverts back from Syntax Tools format to Erlang forms. 550 | %%%

Note that the Erlang forms are a subset of the Syntax Tools 551 | %%% syntax tree, so this function is safe to call even on a list of 552 | %%% regular Erlang forms.

553 | %%%

Note2: R16B03 introduced a bug, where forms produced by 554 | %%% `erl_syntax:revert/1' (specifically, implicit funs) could crash the linter. 555 | %%% This function works around that limitation, after first verifying that it's 556 | %%% necessary to do so. Use of the workaround can be forced with the help of 557 | %%% the `parse_trans' environment variable {revert_workaround, true}. This 558 | %%% variable will be removed when R16B03 is no longer 'supported'.

559 | %%% @end 560 | %%% 561 | -spec revert(forms()) -> 562 | forms(). 563 | revert(Tree) when is_list(Tree) -> 564 | WorkAround = needs_revert_workaround(), 565 | [revert_form(T, WorkAround) || T <- lists:flatten(Tree)]. 566 | 567 | %%% @spec (Tree) -> Form 568 | %%% 569 | %%% @doc Reverts a single form back from Syntax Tools format to Erlang forms. 570 | %%%

`erl_syntax:revert/1' has had a long-standing bug where it doesn't 571 | %%% completely revert attribute forms. This function deals properly with those 572 | %%% cases.

573 | %%%

Note that the Erlang forms are a subset of the Syntax Tools 574 | %%% syntax tree, so this function is safe to call even on a regular Erlang 575 | %%% form.

576 | %%%

Note2: R16B03 introduced a bug, where forms produced by 577 | %%% `erl_syntax:revert/1' (specifically, implicit funs) could crash the linter. 578 | %%% This function works around that limitation, after first verifying that it's 579 | %%% necessary to do so. Use of the workaround can be forced with the help of 580 | %%% the `parse_trans' environment variable {revert_workaround, true}. This 581 | %%% variable will be removed when R16B03 is no longer 'supported'.

582 | %%% @end 583 | revert_form(F) -> 584 | revert_form(F, needs_revert_workaround()). 585 | 586 | revert_form(F, W) -> 587 | case erl_syntax:revert(F) of 588 | {attribute,L,A,Tree} when element(1,Tree) == tree -> 589 | {attribute,L,A,erl_syntax:revert(Tree)}; 590 | Result -> 591 | if W -> fix_impl_fun(Result); 592 | true -> Result 593 | end 594 | end. 595 | 596 | fix_impl_fun({'fun',L,{function,{atom,_,Fn},{integer,_,Ay}}}) -> 597 | {'fun',L,{function,Fn,Ay}}; 598 | fix_impl_fun({'fun',L,{function,{atom,_,M},{atom,_,Fn},{integer,_,Ay}}}) -> 599 | {'fun',L,{function,M,Fn,Ay}}; 600 | fix_impl_fun(T) when is_tuple(T) -> 601 | list_to_tuple([fix_impl_fun(F) || F <- tuple_to_list(T)]); 602 | fix_impl_fun([H|T]) -> 603 | [fix_impl_fun(H) | fix_impl_fun(T)]; 604 | fix_impl_fun(X) -> 605 | X. 606 | 607 | needs_revert_workaround() -> 608 | case application:get_env(parse_trans,revert_workaround) of 609 | {ok, Bool} when is_boolean(Bool) -> Bool; 610 | _ -> 611 | Res = try lint_reverted() 612 | catch 613 | error:_ -> 614 | true 615 | end, 616 | application:set_env(parse_trans,revert_workaround,Res), 617 | Res 618 | end. 619 | 620 | lint_reverted() -> 621 | Ts = [{attribute,1,module,m}, 622 | {attribute,2,export,[{f,0}]}, 623 | erl_syntax:function(erl_syntax:atom(f), 624 | [erl_syntax:clause( 625 | [], 626 | [erl_syntax:implicit_fun( 627 | erl_syntax:atom(f), 628 | erl_syntax:integer(0))])])], 629 | Rev = erl_syntax:revert_forms(Ts), 630 | erl_lint:module(Rev), 631 | false. 632 | 633 | 634 | %%% @spec (Forms, Context) -> Forms | {error,Es,Ws} | {warnings,Forms,Ws} 635 | %%% 636 | %%% @doc Checks the transformed result for errors and warnings 637 | %%%

Errors and warnings can be produced from inside a parse transform, with 638 | %%% a bit of care. The easiest way is to simply produce an `{error, Err}' or 639 | %%% `{warning, Warn}' form in place. This function finds such forms, and 640 | %%% removes them from the form list (otherwise, the linter will crash), and 641 | %%% produces a return value that the compiler can work with.

642 | %%% 643 | %%% The format of the `error' and `warning' "forms" must be 644 | %%% `{Tag, {Pos, Module, Info}}', where: 645 | %%%
    646 | %%%
  • `Tag :: error | warning'
  • 647 | %%%
  • `Pos :: LineNumber | {LineNumber, ColumnNumber}'
  • 648 | %%%
  • `Module' is a module that exports a corresponding 649 | %%% `Module:format_error(Info)'
  • 650 | %%%
  • `Info :: term()'
  • 651 | %%%
652 | %%%

If the error is in the form of a caught exception, `Info' may be produced 653 | %%% using the function {@link format_exception/2}.

654 | %%% @end 655 | return(Forms, Context) -> 656 | JustForms = plain_transform( 657 | fun({error,_}) -> skip; 658 | ({warning,_}) -> skip; 659 | (_) -> continue 660 | end, Forms), 661 | File = case Context of 662 | #context{file = F} -> F; 663 | _ -> "parse_transform" 664 | end, 665 | case {find_forms(Forms, error), find_forms(Forms, warning)} of 666 | {[], []} -> 667 | JustForms; 668 | {[], Ws} -> 669 | {warnings, JustForms, [{File, [W || {warning,W} <- Ws]}]}; 670 | {Es, Ws} -> 671 | {error, 672 | [{File, [E || {error,E} <- Es]}], 673 | [{File, [W || {warning,W} <- Ws]}]} 674 | end. 675 | 676 | find_forms([H|T], Tag) when element(1, H) == Tag -> 677 | [H|find_forms(T, Tag)]; 678 | find_forms([H|T], Tag) when is_tuple(H) -> 679 | find_forms(tuple_to_list(H), Tag) ++ find_forms(T, Tag); 680 | find_forms([H|T], Tag) when is_list(H) -> 681 | find_forms(H, Tag) ++ find_forms(T, Tag); 682 | find_forms([_|T], Tag) -> 683 | find_forms(T, Tag); 684 | find_forms([], _) -> 685 | []. 686 | 687 | 688 | -define(LINEMAX, 5). 689 | -define(CHAR_MAX, 60). 690 | 691 | %%% @spec (Class, Reason) -> String 692 | %%% @equiv format_exception(Class, Reason, 4) 693 | format_exception(Class, Reason) -> 694 | format_exception(Class, Reason, 4). 695 | 696 | %%% @spec (Class, Reason, Lines) -> String 697 | %%% Class = error | throw | exit 698 | %%% Reason = term() 699 | %%% Lines = integer() | infinity 700 | %%% 701 | %%% @doc Produces a few lines of user-friendly formatting of exception info 702 | %%% 703 | %%% This function is very similar to the exception pretty-printing in the shell, 704 | %%% but returns a string that can be used as error info e.g. by error forms 705 | %%% handled by {@link return/2}. By default, the first 4 lines of the 706 | %%% pretty-printed exception info are returned, but this can be controlled 707 | %%% with the `Lines' parameter. 708 | %%% 709 | %%% Note that a stacktrace is generated inside this function. 710 | %%% @end 711 | format_exception(Class, Reason, Lines) -> 712 | PrintF = fun(Term, I) -> 713 | io_lib_pretty:print( 714 | Term, I, columns(), ?LINEMAX, ?CHAR_MAX, 715 | record_print_fun()) 716 | end, 717 | StackF = fun(_, _, _) -> false end, 718 | lines(Lines, lib:format_exception( 719 | 1, Class, Reason, erlang:get_stacktrace(), StackF, PrintF)). 720 | 721 | columns() -> 722 | case io:columns() of 723 | {ok, N} -> N; 724 | _-> 80 725 | end. 726 | 727 | lines(infinity, S) -> S; 728 | lines(N, S) -> 729 | [L1|Ls] = re:split(iolist_to_binary([S]), <<"\n">>, [{return,list}]), 730 | [L1|["\n" ++ L || L <- lists:sublist(Ls, 1, N-1)]]. 731 | 732 | record_print_fun() -> 733 | fun(_,_) -> no end. 734 | 735 | %%% @spec (Attr, Context) -> any() 736 | %%% Attr = module | function | arity | options 737 | %%% 738 | %%% @doc 739 | %%% Accessor function for the Context record. 740 | %%% @end 741 | -spec context(atom(), #context{}) -> 742 | term(). 743 | context(module, #context{module = M} ) -> M; 744 | context(function, #context{function = F}) -> F; 745 | context(arity, #context{arity = A} ) -> A; 746 | context(file, #context{file = F} ) -> F; 747 | context(options, #context{options = O} ) -> O. 748 | 749 | 750 | -spec do_inspect(insp_f(), term(), forms(), #context{}) -> 751 | term(). 752 | do_inspect(F, Acc, Forms, Context) -> 753 | F1 = 754 | fun(Form, Acc0) -> 755 | Type = type(Form), 756 | {Recurse, Acc1} = apply_F(F, Type, Form, Context, Acc0), 757 | if_recurse( 758 | Recurse, Form, _Else = Acc1, 759 | fun(ListOfLists) -> 760 | lists:foldl( 761 | fun(L, AccX) -> 762 | do_inspect( 763 | F, AccX, L, 764 | update_context(Form, Context)) 765 | end, Acc1, ListOfLists) 766 | end) 767 | end, 768 | lists:foldl(F1, Acc, Forms). 769 | 770 | if_recurse(true, Form, Else, F) -> recurse(Form, Else, F); 771 | if_recurse(false, _, Else, _) -> Else. 772 | 773 | recurse(Form, Else, F) -> 774 | case erl_syntax:subtrees(Form) of 775 | [] -> 776 | Else; 777 | [_|_] = ListOfLists -> 778 | F(ListOfLists) 779 | end. 780 | 781 | -spec do_transform(xform_f_rec(), term(), forms(), #context{}) -> 782 | {forms(), term()}. 783 | do_transform(F, Acc, Forms, Context) -> 784 | Rec = fun do_transform/4, % this function 785 | F1 = 786 | fun(Form, Acc0) -> 787 | {Before1, Form1, After1, Recurse, Acc1} = 788 | this_form_rec(F, Form, Context, Acc0), 789 | if Recurse -> 790 | {NewForm, NewAcc} = 791 | enter_subtrees(Form1, F, 792 | update_context(Form1, Context), Acc1, Rec), 793 | {Before1, NewForm, After1, NewAcc}; 794 | true -> 795 | {Before1, Form1, After1, Acc1} 796 | end 797 | end, 798 | mapfoldl(F1, Acc, Forms). 799 | 800 | -spec do_depth_first(xform_f_df(), term(), forms(), #context{}) -> 801 | {forms(), term()}. 802 | do_depth_first(F, Acc, Forms, Context) -> 803 | Rec = fun do_depth_first/4, % this function 804 | F1 = 805 | fun(Form, Acc0) -> 806 | {NewForm, NewAcc} = 807 | enter_subtrees(Form, F, Context, Acc0, Rec), 808 | this_form_df(F, NewForm, Context, NewAcc) 809 | end, 810 | mapfoldl(F1, Acc, Forms). 811 | 812 | enter_subtrees(Form, F, Context, Acc, Recurse) -> 813 | case erl_syntax:subtrees(Form) of 814 | [] -> 815 | {Form, Acc}; 816 | [_|_] = ListOfLists -> 817 | {NewListOfLists, NewAcc} = 818 | mapfoldl( 819 | fun(L, AccX) -> 820 | Recurse(F, AccX, L, Context) 821 | end, Acc, ListOfLists), 822 | NewForm = 823 | erl_syntax:update_tree( 824 | Form, NewListOfLists), 825 | {NewForm, NewAcc} 826 | end. 827 | 828 | 829 | this_form_rec(F, Form, Context, Acc) -> 830 | Type = type(Form), 831 | case apply_F(F, Type, Form, Context, Acc) of 832 | {Form1x, Rec1x, A1x} -> 833 | {[], Form1x, [], Rec1x, A1x}; 834 | {_Be1, _F1, _Af1, _Rec1, _Ac1} = Res1 -> 835 | Res1 836 | end. 837 | this_form_df(F, Form, Context, Acc) -> 838 | Type = type(Form), 839 | case apply_F(F, Type, Form, Context, Acc) of 840 | {Form1x, A1x} -> 841 | {[], Form1x, [], A1x}; 842 | {_Be1, _F1, _Af1, _Ac1} = Res1 -> 843 | Res1 844 | end. 845 | 846 | apply_F(F, Type, Form, Context, Acc) -> 847 | try F(Type, Form, Context, Acc) 848 | catch 849 | error:Reason -> 850 | ?ERROR(Reason, 851 | ?HERE, 852 | [{type, Type}, 853 | {context, Context}, 854 | {acc, Acc}, 855 | {apply_f, F}, 856 | {form, Form}] ++ [{stack, erlang:get_stacktrace()}]) 857 | end. 858 | 859 | 860 | update_context(Form, Context0) -> 861 | case type(Form) of 862 | function -> 863 | {Fun, Arity} = 864 | erl_syntax_lib:analyze_function(Form), 865 | Context0#context{function = Fun, 866 | arity = Arity}; 867 | _ -> 868 | Context0 869 | end. 870 | 871 | 872 | 873 | 874 | %%% Slightly modified version of lists:mapfoldl/3 875 | %%% Here, F/2 is able to insert forms before and after the form 876 | %%% in question. The inserted forms are not transformed afterwards. 877 | mapfoldl(F, Accu0, [Hd|Tail]) -> 878 | {Before, Res, After, Accu1} = 879 | case F(Hd, Accu0) of 880 | {Be, _, Af, _} = Result when is_list(Be), is_list(Af) -> 881 | Result; 882 | {R1, A1} -> 883 | {[], R1, [], A1} 884 | end, 885 | {Rs, Accu2} = mapfoldl(F, Accu1, Tail), 886 | {Before ++ [Res| After ++ Rs], Accu2}; 887 | mapfoldl(F, Accu, []) when is_function(F, 2) -> {[], Accu}. 888 | 889 | 890 | rpt_error(_Reason, _Fun, _Info, _Trace) -> 891 | %% Fmt = lists:flatten( 892 | %% ["*** ERROR in parse_transform function:~n" 893 | %% "*** Reason = ~p~n", 894 | %% "*** Location: ~p~n", 895 | %% "*** Trace: ~p~n", 896 | %% ["*** ~10w = ~p~n" || _ <- Info]]), 897 | %% Args = [Reason, Fun, Trace | 898 | %% lists:foldr( 899 | %% fun({K,V}, Acc) -> 900 | %% [K, V | Acc] 901 | %% end, [], Info)], 902 | %%io:format(Fmt, Args), 903 | ok. 904 | 905 | -spec format_error({atom(), term()}) -> 906 | iolist(). 907 | format_error({E, [{M,F,A}|_]} = Error) -> 908 | try lists:flatten(io_lib:fwrite("~p in ~s:~s/~s", [E, atom_to_list(M), 909 | atom_to_list(F), integer_to_list(A)])) 910 | catch 911 | error:_ -> 912 | format_error_(Error) 913 | end; 914 | format_error(Error) -> 915 | format_error_(Error). 916 | 917 | format_error_(Error) -> 918 | lists:flatten(io_lib:fwrite("~p", [Error])). 919 | -------------------------------------------------------------------------------- /src/exprecs.erl: -------------------------------------------------------------------------------- 1 | %%% The contents of this file are subject to the Erlang Public License, 2 | %%% Version 1.1, (the "License"); you may not use this file except in 3 | %%% compliance with the License. You may obtain a copy of the License at 4 | %%% http://www.erlang.org/EPLICENSE 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 8 | %% the License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is exprecs-0.2. 12 | %% 13 | %% Copyright (c) 2010 Erlang Solutions Ltd. 14 | %% The Initial Developer of the Original Code is Ericsson AB. 15 | %% Portions created by Ericsson are Copyright (C), 2006, Ericsson AB. 16 | %% All Rights Reserved. 17 | %% 18 | %% Contributor(s): ______________________________________. 19 | 20 | %%------------------------------------------------------------------- 21 | %% File : exprecs.erl 22 | %% @author : Ulf Wiger 23 | %% @end 24 | %% Description : 25 | %% 26 | %% Created : 13 Feb 2006 by Ulf Wiger 27 | %% Rewritten: Jan-Feb 2010 by Ulf Wiger 28 | %%------------------------------------------------------------------- 29 | 30 | %% @doc Parse transform for generating record access functions. 31 | %%

This parse transform can be used to reduce compile-time 32 | %% dependencies in large systems.

33 | %%

In the old days, before records, Erlang programmers often wrote 34 | %% access functions for tuple data. This was tedious and error-prone. 35 | %% The record syntax made this easier, but since records were implemented 36 | %% fully in the pre-processor, a nasty compile-time dependency was 37 | %% introduced.

38 | %%

This module automates the generation of access functions for 39 | %% records. While this method cannot fully replace the utility of 40 | %% pattern matching, it does allow a fair bit of functionality on 41 | %% records without the need for compile-time dependencies.

42 | %%

Whenever record definitions need to be exported from a module, 43 | %% inserting a compiler attribute, 44 | %% export_records([RecName|...]) causes this transform 45 | %% to lay out access functions for the exported records:

46 | %% 47 | %% As an example, consider the following module: 48 | %%
  49 | %% -module(test_exprecs).
  50 | %% -export([f/0]).
  51 | %%
  52 | %% -compile({parse_transform, exprecs}).
  53 | %%
  54 | %% -record(r, {a = 0 :: integer(),
  55 | %%             b = 0 :: integer(),
  56 | %%             c = 0 :: integer()}).
  57 | %%
  58 | %% -record(s,{a}).
  59 | %%
  60 | %% -export_records([r,s]).
  61 | %%
  62 | %% f() ->
  63 | %%     {new,'#new-r'([])}.
  64 | %% 
65 | %% 66 | %% Compiling this (assuming exprecs is in the path) will produce the 67 | %% following code. 68 | %% 69 | %%
  70 | %% -module(test_exprecs).
  71 | %% -compile({pt_pp_src,true}).
  72 | %% -export([f/0]).
  73 | %% -record(r,{a = 0 :: integer(),b = 0 :: integer(),c = 0 :: integer()}).
  74 | %% -record(s,{a}).
  75 | %% -export_records([r,s]).
  76 | %% -export(['#exported_records-'/0,
  77 | %%          '#new-'/1,
  78 | %%          '#info-'/1,
  79 | %%          '#info-'/2,
  80 | %%          '#pos-'/2,
  81 | %%          '#is_record-'/1,
  82 | %%          '#is_record-'/2,
  83 | %%          '#get-'/2,
  84 | %%          '#set-'/2,
  85 | %%          '#fromlist-'/2,
  86 | %%          '#lens-'/2,
  87 | %%          '#new-r'/0,
  88 | %%          '#new-r'/1,
  89 | %%          '#get-r'/2,
  90 | %%          '#set-r'/2,
  91 | %%          '#pos-r'/1,
  92 | %%          '#fromlist-r'/1,
  93 | %%          '#fromlist-r'/2,
  94 | %%          '#info-r'/1,
  95 | %%          '#lens-r'/1,
  96 | %%          '#new-s'/0,
  97 | %%          '#new-s'/1,
  98 | %%          '#get-s'/2,
  99 | %%          '#set-s'/2,
 100 | %%          '#pos-s'/1,
 101 | %%          '#fromlist-s'/1,
 102 | %%          '#fromlist-s'/2,
 103 | %%          '#info-s'/1,
 104 | %%          '#lens-s'/1]).
 105 | %%
 106 | %% -type '#prop-r'() :: {a, integer()} | {b, integer()} | {c, integer()}.
 107 | %% -type '#attr-r'() :: a | b | c.
 108 | %% -type '#prop-s'() :: {a, any()}.
 109 | %% -type '#attr-s'() :: a.
 110 | %%
 111 | %% -spec '#exported_records-'() -> [r | s].
 112 | %% '#exported_records-'() ->
 113 | %%     [r,s].
 114 | %%
 115 | %% -spec '#new-'(r) -> #r{};
 116 | %%              (s) -> #s{}.
 117 | %% '#new-'(r) ->
 118 | %%     '#new-r'();
 119 | %% '#new-'(s) ->
 120 | %%     '#new-s'().
 121 | %%
 122 | %% -spec '#info-'(r) -> [a | b | c];
 123 | %%               (s) -> [a].
 124 | %% '#info-'(RecName) ->
 125 | %%     '#info-'(RecName, fields).
 126 | %%
 127 | %% -spec '#info-'(r, size) -> 4;
 128 | %%               (r, fields) -> [a | b | c];
 129 | %%               (s, size) -> 2;
 130 | %%               (s, fields) -> [a].
 131 | %% '#info-'(r, Info) ->
 132 | %%     '#info-r'(Info);
 133 | %% '#info-'(s, Info) ->
 134 | %%     '#info-s'(Info).
 135 | %%
 136 | %% -spec '#pos-'(r, a) -> 1;
 137 | %%              (r, b) -> 2;
 138 | %%              (r, c) -> 3;
 139 | %%              (s, a) -> 1.
 140 | %% '#pos-'(r, Attr) ->
 141 | %%     '#pos-r'(Attr);
 142 | %% '#pos-'(s, Attr) ->
 143 | %%     '#pos-s'(Attr).
 144 | %%
 145 | %% -spec '#is_record-'(any()) -> boolean().
 146 | %%                    
 147 | %% '#is_record-'(X) ->
 148 | %%     if
 149 | %%         is_record(X, r) ->
 150 | %%             true;
 151 | %%         is_record(X, s) ->
 152 | %%             true;
 153 | %%         true ->
 154 | %%             false
 155 | %%     end.
 156 | %%
 157 | %% -spec '#is_record-'(any(), any()) -> boolean().
 158 | %%                    
 159 | %% '#is_record-'(s, Rec) when tuple_size(Rec) == 2, element(1, Rec) == s ->
 160 | %%     true;
 161 | %% '#is_record-'(r, Rec) when tuple_size(Rec) == 4, element(1, Rec) == r ->
 162 | %%     true;
 163 | %% '#is_record-'(_, _) ->
 164 | %%     false.
 165 | %%
 166 | %% -spec '#get-'(a, #r{}) -> integer();
 167 | %%              (b, #r{}) -> integer();
 168 | %%              (c, #r{}) -> integer();
 169 | %%              (a, #s{}) -> any();
 170 | %%              (['#attr-r'()], #r{}) -> [integer()];
 171 | %%              (['#attr-s'()], #s{}) -> [any()].
 172 | %% '#get-'(Attrs, Rec) when is_record(Rec, r) ->
 173 | %%     '#get-r'(Attrs, Rec);
 174 | %% '#get-'(Attrs, Rec) when is_record(Rec, s) ->
 175 | %%     '#get-s'(Attrs, Rec).
 176 | %%
 177 | %% -spec '#set-'(['#prop-r'()], #r{}) -> #r{};
 178 | %%              (['#prop-s'()], #s{}) -> #s{}.
 179 | %% '#set-'(Vals, Rec) when is_record(Rec, r) ->
 180 | %%     '#set-r'(Vals, Rec);
 181 | %% '#set-'(Vals, Rec) when is_record(Rec, s) ->
 182 | %%     '#set-s'(Vals, Rec).
 183 | %%
 184 | %% -spec '#fromlist-'(['#prop-r'()], #r{}) -> #r{};
 185 | %%                   (['#prop-s'()], #s{}) -> #s{}.
 186 | %% '#fromlist-'(Vals, Rec) when is_record(Rec, r) ->
 187 | %%     '#fromlist-r'(Vals, Rec);
 188 | %% '#fromlist-'(Vals, Rec) when is_record(Rec, s) ->
 189 | %%     '#fromlist-s'(Vals, Rec).
 190 | %%
 191 | %% -spec '#lens-'('#prop-r'(), r) ->
 192 | %%                  {fun((#r{}) -> any()), fun((any(), #r{}) -> #r{})};
 193 | %%              ('#prop-s'(), s) ->
 194 | %%                  {fun((#s{}) -> any()), fun((any(), #s{}) -> #s{})}.
 195 | %% '#lens-'(Attr, r) ->
 196 | %%     '#lens-r'(Attr);
 197 | %% '#lens-'(Attr, s) ->
 198 | %%     '#lens-s'(Attr).
 199 | %%
 200 | %% -spec '#new-r'() -> #r{}.
 201 | %% '#new-r'() ->
 202 | %%     #r{}.
 203 | %%
 204 | %% -spec '#new-r'(['#prop-r'()]) -> #r{}.
 205 | %% '#new-r'(Vals) ->
 206 | %%     '#set-r'(Vals, #r{}).
 207 | %%
 208 | %% -spec '#get-r'(a, #r{}) -> integer();
 209 | %%               (b, #r{}) -> integer();
 210 | %%               (c, #r{}) -> integer();
 211 | %%               (['#attr-r'()], #r{}) -> [integer()].
 212 | %% '#get-r'(Attrs, R) when is_list(Attrs) ->
 213 | %%     [
 214 | %%      '#get-r'(A, R) ||
 215 | %%          A <- Attrs
 216 | %%     ];
 217 | %% '#get-r'(a, R) ->
 218 | %%     R#r.a;
 219 | %% '#get-r'(b, R) ->
 220 | %%     R#r.b;
 221 | %% '#get-r'(c, R) ->
 222 | %%     R#r.c;
 223 | %% '#get-r'(Attr, R) ->
 224 | %%     error(bad_record_op, ['#get-r',Attr,R]).
 225 | %%
 226 | %% -spec '#set-r'(['#prop-r'()], #r{}) -> #r{}.
 227 | %% '#set-r'(Vals, Rec) ->
 228 | %%     F = fun([], R, _F1) ->
 229 | %%                R;
 230 | %%            ([{a,V}|T], R, F1) when is_list(T) ->
 231 | %%                F1(T, R#r{a = V}, F1);
 232 | %%            ([{b,V}|T], R, F1) when is_list(T) ->
 233 | %%                F1(T, R#r{b = V}, F1);
 234 | %%            ([{c,V}|T], R, F1) when is_list(T) ->
 235 | %%                F1(T, R#r{c = V}, F1);
 236 | %%            (Vs, R, _) ->
 237 | %%                error(bad_record_op, ['#set-r',Vs,R])
 238 | %%         end,
 239 | %%     F(Vals, Rec, F).
 240 | %%
 241 | %% -spec '#fromlist-r'(['#prop-r'()]) -> #r{}.
 242 | %% '#fromlist-r'(Vals) when is_list(Vals) ->
 243 | %%     '#fromlist-r'(Vals, '#new-r'()).
 244 | %%
 245 | %% -spec '#fromlist-r'(['#prop-r'()], #r{}) -> #r{}.
 246 | %% '#fromlist-r'(Vals, Rec) ->
 247 | %%     AttrNames = [{a,2},{b,3},{c,4}],
 248 | %%     F = fun([], R, _F1) ->
 249 | %%                R;
 250 | %%            ([{H,Pos}|T], R, F1) when is_list(T) ->
 251 | %%                case lists:keyfind(H, 1, Vals) of
 252 | %%                    false ->
 253 | %%                        F1(T, R, F1);
 254 | %%                    {_,Val} ->
 255 | %%                        F1(T, setelement(Pos, R, Val), F1)
 256 | %%                end
 257 | %%         end,
 258 | %%     F(AttrNames, Rec, F).
 259 | %%
 260 | %% -spec '#pos-r'('#attr-r'() | atom()) -> integer().
 261 | %% '#pos-r'(a) ->
 262 | %%     2;
 263 | %% '#pos-r'(b) ->
 264 | %%     3;
 265 | %% '#pos-r'(c) ->
 266 | %%     4;
 267 | %% '#pos-r'(A) when is_atom(A) ->
 268 | %%     0.
 269 | %%
 270 | %% -spec '#info-r'(fields) -> [a | b | c];
 271 | %%                (size) -> 3.
 272 | %% '#info-r'(fields) ->
 273 | %%     record_info(fields, r);
 274 | %% '#info-r'(size) ->
 275 | %%     record_info(size, r).
 276 | %%
 277 | %% -spec '#lens-r'('#prop-r'()) ->
 278 | %%                   {fun((#r{}) -> any()), fun((any(), #r{}) -> #r{})}.
 279 | %% '#lens-r'(a) ->
 280 | %%     {fun(R) ->
 281 | %%             '#get-r'(a, R)
 282 | %%      end,
 283 | %%      fun(X, R) ->
 284 | %%             '#set-r'([{a,X}], R)
 285 | %%      end};
 286 | %% '#lens-r'(b) ->
 287 | %%     {fun(R) ->
 288 | %%             '#get-r'(b, R)
 289 | %%      end,
 290 | %%      fun(X, R) ->
 291 | %%             '#set-r'([{b,X}], R)
 292 | %%      end};
 293 | %% '#lens-r'(c) ->
 294 | %%     {fun(R) ->
 295 | %%             '#get-r'(c, R)
 296 | %%      end,
 297 | %%      fun(X, R) ->
 298 | %%             '#set-r'([{c,X}], R)
 299 | %%      end};
 300 | %% '#lens-r'(Attr) ->
 301 | %%     error(bad_record_op, ['#lens-r',Attr]).
 302 | %%
 303 | %% -spec '#new-s'() -> #s{}.
 304 | %% '#new-s'() ->
 305 | %%     #s{}.
 306 | %%
 307 | %% -spec '#new-s'(['#prop-s'()]) -> #s{}.
 308 | %% '#new-s'(Vals) ->
 309 | %%     '#set-s'(Vals, #s{}).
 310 | %%
 311 | %% -spec '#get-s'(a, #s{}) -> any();
 312 | %%               (['#attr-s'()], #s{}) -> [any()].
 313 | %% '#get-s'(Attrs, R) when is_list(Attrs) ->
 314 | %%     [
 315 | %%      '#get-s'(A, R) ||
 316 | %%          A <- Attrs
 317 | %%     ];
 318 | %% '#get-s'(a, R) ->
 319 | %%     R#s.a;
 320 | %% '#get-s'(Attr, R) ->
 321 | %%     error(bad_record_op, ['#get-s',Attr,R]).
 322 | %%
 323 | %% -spec '#set-s'(['#prop-s'()], #s{}) -> #s{}.
 324 | %% '#set-s'(Vals, Rec) ->
 325 | %%     F = fun([], R, _F1) ->
 326 | %%                R;
 327 | %%            ([{a,V}|T], R, F1) when is_list(T) ->
 328 | %%                F1(T, R#s{a = V}, F1);
 329 | %%            (Vs, R, _) ->
 330 | %%                error(bad_record_op, ['#set-s',Vs,R])
 331 | %%         end,
 332 | %%     F(Vals, Rec, F).
 333 | %%
 334 | %% -spec '#fromlist-s'(['#prop-s'()]) -> #s{}.
 335 | %% '#fromlist-s'(Vals) when is_list(Vals) ->
 336 | %%     '#fromlist-s'(Vals, '#new-s'()).
 337 | %%
 338 | %% -spec '#fromlist-s'(['#prop-s'()], #s{}) -> #s{}.
 339 | %% '#fromlist-s'(Vals, Rec) ->
 340 | %%     AttrNames = [{a,2}],
 341 | %%     F = fun([], R, _F1) ->
 342 | %%                R;
 343 | %%            ([{H,Pos}|T], R, F1) when is_list(T) ->
 344 | %%                case lists:keyfind(H, 1, Vals) of
 345 | %%                    false ->
 346 | %%                        F1(T, R, F1);
 347 | %%                    {_,Val} ->
 348 | %%                        F1(T, setelement(Pos, R, Val), F1)
 349 | %%                end
 350 | %%         end,
 351 | %%     F(AttrNames, Rec, F).
 352 | %%
 353 | %% -spec '#pos-s'('#attr-s'() | atom()) -> integer().
 354 | %% '#pos-s'(a) ->
 355 | %%     2;
 356 | %% '#pos-s'(A) when is_atom(A) ->
 357 | %%     0.
 358 | %%
 359 | %% -spec '#info-s'(fields) -> [a];
 360 | %%                (size) -> 1.
 361 | %% '#info-s'(fields) ->
 362 | %%     record_info(fields, s);
 363 | %% '#info-s'(size) ->
 364 | %%     record_info(size, s).
 365 | %%
 366 | %% -spec '#lens-s'('#prop-s'()) ->
 367 | %%                   {fun((#s{}) -> any()), fun((any(), #s{}) -> #s{})}.
 368 | %% '#lens-s'(a) ->
 369 | %%     {fun(R) ->
 370 | %%             '#get-s'(a, R)
 371 | %%      end,
 372 | %%      fun(X, R) ->
 373 | %%             '#set-s'([{a,X}], R)
 374 | %%      end};
 375 | %% '#lens-s'(Attr) ->
 376 | %%     error(bad_record_op, ['#lens-s', Attr]).
 377 | %%
 378 | %% f() ->
 379 | %%     {new,'#new-r'([])}.
 380 | %%
 381 | %% 
382 | %% 383 | %% It is possible to modify the naming rules of exprecs, through the use 384 | %% of the following attributes (example reflecting the current rules): 385 | %% 386 | %%
 387 | %% -exprecs_prefix(["#", operation, "-"]).
 388 | %% -exprecs_fname([prefix, record]).
 389 | %% -exprecs_vfname([fname, "__", version]).
 390 | %% 
391 | %% 392 | %% The lists must contain strings or any of the following control atoms: 393 | %%
    394 | %%
  • in `exprecs_prefix': `operation'
  • 395 | %%
  • in `exprecs_fname': `operation', `record', `prefix'
  • 396 | %%
  • in `exprecs_vfname': `operation', `record', `prefix', `fname', `version' 397 | %%
  • 398 | %%
399 | %% 400 | %% Exprecs will substitute the control atoms with the string values of the 401 | %% corresponding items. The result will then be flattened and converted to an 402 | %% atom (a valid function or type name). 403 | %% 404 | %% `operation' is one of: 405 | %%
406 | %%
`new'
Creates a new record
407 | %%
`get'
Retrieves given attribute values from a record
408 | %%
`set'
Sets given attribute values in a record
409 | %%
`fromlist'
Creates a record from a key-value list
410 | %%
`info'
Equivalent to record_info/2
411 | %%
`pos'
Returns the position of a given attribute
412 | %%
`is_record'
Tests if a value is a specific record
413 | %%
`convert'
Converts an old record to the current version
414 | %%
`prop'
Used only in type specs
415 | %%
`attr'
Used only in type specs
416 | %%
`lens'
Returns a 'lens' (an accessor pair) as described in 417 | %% [http://github.com/jlouis/erl-lenses]
418 | %%
419 | %% 420 | %% @end 421 | 422 | -module(exprecs). 423 | 424 | -export([parse_transform/2, 425 | format_error/1, 426 | % transform/3, 427 | context/2]). 428 | 429 | -record(context, {module, 430 | function, 431 | arity}). 432 | 433 | -record(pass1, {exports = [], 434 | generated = false, 435 | records = [], 436 | record_types = [], 437 | versions = orddict:new(), 438 | inserted = false, 439 | prefix = ["#", operation, "-"], 440 | fname = [prefix, record], 441 | vfname = [fname, "__", version]}). 442 | 443 | -include("../include/codegen.hrl"). 444 | 445 | -define(HERE, {?MODULE, ?LINE}). 446 | 447 | -define(ERROR(R, F, I), 448 | begin 449 | rpt_error(R, F, I), 450 | throw({error,get_pos(I),{unknown,R}}) 451 | end). 452 | 453 | -type form() :: any(). 454 | -type forms() :: [form()]. 455 | -type options() :: [{atom(), any()}]. 456 | 457 | 458 | get_pos(I) -> 459 | case proplists:get_value(form, I) of 460 | undefined -> 461 | 0; 462 | Form -> 463 | erl_syntax:get_pos(Form) 464 | end. 465 | 466 | -spec parse_transform(forms(), options()) -> 467 | forms(). 468 | parse_transform(Forms, Options) -> 469 | parse_trans:top(fun do_transform/2, Forms, Options). 470 | 471 | do_transform(Forms, Context) -> 472 | Acc1 = versioned_records( 473 | add_untyped_recs( 474 | parse_trans:do_inspect(fun inspect_f/4, #pass1{}, 475 | Forms, Context))), 476 | {Forms2, Acc2} = 477 | parse_trans:do_transform(fun generate_f/4, Acc1, Forms, Context), 478 | parse_trans:revert(verify_generated(Forms2, Acc2, Context)). 479 | 480 | add_untyped_recs(#pass1{records = Rs, 481 | record_types = RTypes, 482 | exports = Es} = Acc) -> 483 | Untyped = 484 | [{R, Def} || {R, Def} <- Rs, 485 | lists:member(R, Es), 486 | not lists:keymember(R, 1, RTypes)], 487 | RTypes1 = [{R, lists:map( 488 | fun({record_field,L,{atom,_,A}}) -> {A, t_any(L)}; 489 | ({record_field,L,{atom,_,A},_}) -> {A, t_any(L)} 490 | end, Def)} || {R, Def} <- Untyped], 491 | Acc#pass1{record_types = RTypes ++ RTypes1}. 492 | 493 | inspect_f(attribute, {attribute,_L,exprecs_prefix,Pattern}, _Ctxt, Acc) -> 494 | {false, Acc#pass1{prefix = Pattern}}; 495 | inspect_f(attribute, {attribute,_L,exprecs_fname,Pattern}, _Ctxt, Acc) -> 496 | {false, Acc#pass1{fname = Pattern}}; 497 | inspect_f(attribute, {attribute,_L,exprecs_vfname,Pattern}, _Ctxt, Acc) -> 498 | {false, Acc#pass1{vfname = Pattern}}; 499 | inspect_f(attribute, {attribute,_L,record,RecDef}, _Ctxt, Acc) -> 500 | Recs0 = Acc#pass1.records, 501 | {false, Acc#pass1{records = [RecDef|Recs0]}}; 502 | inspect_f(attribute, {attribute,_L,export_records, E}, _Ctxt, Acc) -> 503 | Exports0 = Acc#pass1.exports, 504 | NewExports = Exports0 ++ E, 505 | {false, Acc#pass1{exports = NewExports}}; 506 | inspect_f(attribute, {attribute, _L, type, 507 | {{record, R}, RType,_}}, _Ctxt, Acc) -> 508 | Type = lists:map( 509 | fun({typed_record_field, {record_field,_,{atom,_,A}}, T}) -> 510 | {A, T}; 511 | ({typed_record_field, {record_field,_,{atom,_,A},_}, T}) -> 512 | {A, T}; 513 | ({record_field, _, {atom,L,A}, _}) -> 514 | {A, t_any(L)}; 515 | ({record_field, _, {atom,L,A}}) -> 516 | {A, t_any(L)} 517 | end, RType), 518 | {false, Acc#pass1{record_types = [{R, Type}|Acc#pass1.record_types]}}; 519 | inspect_f(_Type, _Form, _Context, Acc) -> 520 | {false, Acc}. 521 | 522 | generate_f(attribute, {attribute,L,export_records,_} = Form, _Ctxt, 523 | #pass1{exports = [_|_] = Es, versions = Vsns, 524 | inserted = false} = Acc) -> 525 | case check_record_names(Es, L, Acc) of 526 | ok -> continue; 527 | {error, Bad} -> 528 | ?ERROR(invalid_record_exports, ?HERE, Bad) 529 | end, 530 | Exports = [{fname(exported_records, Acc), 0}, 531 | {fname(new, Acc), 1}, 532 | {fname(info, Acc), 1}, 533 | {fname(info, Acc), 2}, 534 | {fname(pos, Acc), 2}, 535 | {fname(is_record, Acc), 1}, 536 | {fname(is_record, Acc), 2}, 537 | {fname(get, Acc), 2}, 538 | {fname(set, Acc), 2}, 539 | {fname(fromlist, Acc), 2}, 540 | {fname(lens, Acc), 2} | 541 | lists:flatmap( 542 | fun(Rec) -> 543 | RecS = atom_to_list(Rec), 544 | FNew = fname(new, RecS, Acc), 545 | [{FNew, 0}, {FNew,1}, 546 | {fname(get, RecS, Acc), 2}, 547 | {fname(set, RecS, Acc), 2}, 548 | {fname(pos, RecS, Acc), 1}, 549 | {fname(fromlist, RecS, Acc), 1}, 550 | {fname(fromlist, RecS, Acc), 2}, 551 | {fname(info, RecS, Acc), 1}, 552 | {fname(lens, RecS, Acc), 1}] 553 | end, Es)] ++ version_exports(Vsns, Acc), 554 | {[], Form, 555 | [{attribute,L,export,Exports}], 556 | false, Acc#pass1{inserted = true}}; 557 | generate_f(function, Form, _Context, #pass1{generated = false} = Acc) -> 558 | % Layout record funs before first function 559 | L = erl_syntax:get_pos(Form), 560 | Forms = generate_specs_and_accessors(L, Acc), 561 | {Forms, Form, [], false, Acc#pass1{generated = true}}; 562 | generate_f(_Type, Form, _Ctxt, Acc) -> 563 | {Form, false, Acc}. 564 | 565 | generate_specs_and_accessors(L, #pass1{exports = [_|_] = Es, 566 | record_types = Ts} = Acc) -> 567 | Specs = generate_specs(L, [{R,T} || {R,T} <- Ts, lists:member(R, Es)], Acc), 568 | Funs = generate_accessors(L, Acc), 569 | Specs ++ Funs; 570 | generate_specs_and_accessors(_, _) -> 571 | []. 572 | 573 | verify_generated(Forms, #pass1{} = Acc, _Context) -> 574 | case (Acc#pass1.generated == true) orelse (Acc#pass1.exports == []) of 575 | true -> 576 | Forms; 577 | false -> 578 | % should be re-written to use the parse_trans helper...? 579 | [{eof,Last}|RevForms] = lists:reverse(Forms), 580 | [{function, NewLast, _, _, _}|_] = RevAs = 581 | lists:reverse(generate_specs_and_accessors(Last, Acc)), 582 | lists:reverse([{eof, NewLast+1} | RevAs] ++ RevForms) 583 | end. 584 | 585 | 586 | check_record_names(Es, L, #pass1{records = Rs}) -> 587 | case [E || E <- Es, 588 | not(lists:keymember(E, 1, Rs))] of 589 | [] -> 590 | ok; 591 | Bad -> 592 | {error, [{L,E} || E <- Bad]} 593 | end. 594 | 595 | versioned_records(#pass1{exports = Es, records = Rs} = Pass1) -> 596 | case split_recnames(Rs) of 597 | [] -> 598 | Pass1#pass1{versions = []}; 599 | [_|_] = Versions -> 600 | Exp_vsns = 601 | lists:foldl( 602 | fun(Re, Acc) -> 603 | case orddict:find(atom_to_list(Re), Versions) of 604 | {ok, Vs} -> 605 | orddict:store(Re, Vs, Acc); 606 | error -> 607 | Acc 608 | end 609 | end, orddict:new(), Es), 610 | Pass1#pass1{versions = Exp_vsns} 611 | end. 612 | 613 | version_exports([], _Acc) -> 614 | []; 615 | version_exports([_|_] = _Vsns, Acc) -> 616 | [{list_to_atom(fname_prefix(info, Acc)), 3}, 617 | {list_to_atom(fname_prefix(convert, Acc)), 2}]. 618 | 619 | 620 | version_accessors(_L, #pass1{versions = []}) -> 621 | []; 622 | version_accessors(L, #pass1{versions = Vsns} = Acc) -> 623 | Flat_vsns = flat_versions(Vsns), 624 | [f_convert(Vsns, L, Acc), 625 | f_info_3(Vsns, L, Acc)] 626 | ++ [f_info_1(Rname, Acc, L, V) || {Rname,V} <- Flat_vsns]. 627 | 628 | flat_versions(Vsns) -> 629 | lists:flatmap(fun({R,Vs}) -> 630 | [{R,V} || V <- Vs] 631 | end, Vsns). 632 | 633 | split_recnames(Rs) -> 634 | lists:foldl( 635 | fun({R,_As}, Acc) -> 636 | case re:split(atom_to_list(R), "__", [{return, list}]) of 637 | [Base, V] -> 638 | orddict:append(Base,V,Acc); 639 | [_] -> 640 | Acc 641 | end 642 | end, orddict:new(), Rs). 643 | 644 | generate_specs(L, Specs, Acc) -> 645 | [[ 646 | {attribute, L, type, 647 | {fname(prop, R, Acc), 648 | {type, L, union, 649 | [{type, L, tuple, [{atom,L,A},T]} || {A,T} <- Attrs]}, []}}, 650 | {attribute, L, type, 651 | {fname(attr, R, Acc), 652 | {type, L, union, 653 | [{atom, L, A} || {A,_} <- Attrs]}, []}} 654 | ] || {R, Attrs} <- Specs, Attrs =/= []] ++ 655 | [[{attribute, L, type, 656 | {fname(prop, R, Acc), 657 | {type, L, any, []}, []}}, 658 | {attribute, L, type, 659 | {fname(attr, R, Acc), 660 | {type, L, any, []}, []}}] || {R, []} <- Specs]. 661 | 662 | 663 | generate_accessors(L, Acc) -> 664 | lists:flatten( 665 | [f_exported_recs(Acc, L), 666 | f_new_(Acc, L), 667 | f_info(Acc, L), 668 | f_info_2(Acc, L), 669 | f_pos_2(Acc, L), 670 | f_isrec_1(Acc, L), 671 | f_isrec_2(Acc, L), 672 | f_get(Acc, L), 673 | f_set(Acc, L), 674 | f_fromlist(Acc, L), 675 | f_lens_(Acc, L)| 676 | lists:append( 677 | lists:map( 678 | fun(Rname) -> 679 | Fields = get_flds(Rname, Acc), 680 | [f_new_0(Rname, L, Acc), 681 | f_new_1(Rname, L, Acc), 682 | f_get_2(Rname, Fields, L, Acc), 683 | f_set_2(Rname, Fields, L, Acc), 684 | f_fromlist_1(Rname, L, Acc), 685 | f_fromlist_2(Rname, Fields, L, Acc), 686 | f_pos_1(Rname, Fields, L, Acc), 687 | f_info_1(Rname, Acc, L), 688 | f_lens_1(Rname, Fields, L, Acc)] 689 | end, Acc#pass1.exports))] ++ version_accessors(L, Acc)). 690 | 691 | get_flds(Rname, #pass1{records = Rs}) -> 692 | {_, Flds} = lists:keyfind(Rname, 1, Rs), 693 | lists:map( 694 | fun({record_field,_, {atom,_,N}}) -> N; 695 | ({record_field,_, {atom,_,N}, _}) -> N 696 | end, Flds). 697 | 698 | 699 | fname_prefix(Op, #pass1{prefix = Pat}) -> 700 | lists:flatten( 701 | lists:map(fun(operation) -> str(Op); 702 | (X) -> str(X) 703 | end, Pat)). 704 | %% fname_prefix(Op, #pass1{} = Acc) -> 705 | %% case Op of 706 | %% new -> "#new-"; 707 | %% get -> "#get-"; 708 | %% set -> "#set-"; 709 | %% fromlist -> "#fromlist-"; 710 | %% info -> "#info-"; 711 | %% pos -> "#pos-"; 712 | %% is_record -> "#is_record-"; 713 | %% convert -> "#convert-"; 714 | %% prop -> "#prop-"; 715 | %% attr -> "#attr-" 716 | %% end. 717 | 718 | %% fname_prefix(Op, Rname, Acc) -> 719 | %% fname_prefix(Op, Acc) ++ str(Rname). 720 | 721 | str(A) when is_atom(A) -> 722 | atom_to_list(A); 723 | str(S) when is_list(S) -> 724 | S. 725 | 726 | fname(Op, #pass1{} = Acc) -> 727 | list_to_atom(fname_prefix(Op, Acc)). 728 | %% list_to_atom(fname_prefix(Op, Acc)). 729 | 730 | fname(Op, Rname, #pass1{fname = FPat} = Acc) -> 731 | Prefix = fname_prefix(Op, Acc), 732 | list_to_atom( 733 | lists:flatten( 734 | lists:map(fun(prefix) -> str(Prefix); 735 | (record) -> str(Rname); 736 | (operation) -> str(Op); 737 | (X) -> str(X) 738 | end, FPat))). 739 | %% list_to_atom(fname_prefix(Op, Rname, Acc)). 740 | 741 | fname(Op, Rname, V, #pass1{vfname = VPat} = Acc) -> 742 | list_to_atom( 743 | lists:flatten( 744 | lists:map(fun(prefix) -> fname_prefix(Op, Acc); 745 | (operation) -> str(Op); 746 | (record) -> str(Rname); 747 | (version) -> str(V); 748 | (fname) -> str(fname(Op, Rname, Acc)); 749 | (X) -> str(X) 750 | end, VPat))). 751 | %% list_to_atom(fname_prefix(Op, Rname, Acc) ++ "__" ++ V). 752 | 753 | 754 | %%% Meta functions 755 | 756 | f_exported_recs(#pass1{exports = Es} = Acc, L) -> 757 | Fname = fname(exported_records, Acc), 758 | [funspec(L, Fname, [], 759 | t_list(L, [t_union(L, [t_atom(L, E) || E <- Es])])), 760 | {function, L, Fname, 0, 761 | [{clause, L, [], [], 762 | [erl_parse:abstract(Es, L)]}]} 763 | ]. 764 | 765 | %%% Accessor functions 766 | %%% 767 | f_new_(#pass1{exports = Es} = Acc, L) -> 768 | Fname = fname(new, Acc), 769 | [funspec(L, Fname, [ {[t_atom(L, E)], t_record(L, E)} || 770 | E <- Es ]), 771 | {function, L, fname(new, Acc), 1, 772 | [{clause, L, [{atom, L, Re}], [], 773 | [{call, L, {atom, L, fname(new, Re, Acc)}, []}]} 774 | || Re <- Es]} 775 | ]. 776 | 777 | f_new_0(Rname, L, Acc) -> 778 | Fname = fname(new, Rname, Acc), 779 | [funspec(L, Fname, [], t_record(L, Rname)), 780 | {function, L, fname(new, Rname, Acc), 0, 781 | [{clause, L, [], [], 782 | [{record, L, Rname, []}]}]} 783 | ]. 784 | 785 | 786 | f_new_1(Rname, L, Acc) -> 787 | Fname = fname(new, Rname, Acc), 788 | [funspec(L, Fname, [t_list(L, [t_prop(L, Rname, Acc)])], 789 | t_record(L, Rname)), 790 | {function, L, Fname, 1, 791 | [{clause, L, [{var, L, 'Vals'}], [], 792 | [{call, L, {atom, L, fname(set, Rname, Acc)}, 793 | [{var, L, 'Vals'}, 794 | {record, L, Rname, []} 795 | ]}] 796 | }]}]. 797 | 798 | funspec(L, Fname, [{H,_} | _] = Alts) -> 799 | Arity = length(H), 800 | {attribute, L, spec, 801 | {{Fname, Arity}, 802 | [{type, L, 'fun', [{type, L, product, Head}, Ret]} || 803 | {Head, Ret} <- Alts, 804 | no_empty_union(Head)]}}. 805 | 806 | no_empty_union({type,_,union,[]}) -> 807 | false; 808 | no_empty_union(T) when is_tuple(T) -> 809 | no_empty_union(tuple_to_list(T)); 810 | no_empty_union([H|T]) -> 811 | no_empty_union(H) andalso no_empty_union(T); 812 | no_empty_union(_) -> 813 | true. 814 | 815 | 816 | 817 | 818 | funspec(L, Fname, Head, Returns) -> 819 | Arity = length(Head), 820 | {attribute, L, spec, 821 | {{Fname, Arity}, 822 | [{type, L, 'fun', 823 | [{type, L, product, Head}, Returns]}]}}. 824 | 825 | 826 | t_prop(L, Rname, Acc) -> {type, L, fname(prop, Rname, Acc), []}. 827 | t_attr(L, Rname, Acc) -> {type, L, fname(attr, Rname, Acc), []}. 828 | t_union(L, Alt) -> {type, L, union, lists:usort(Alt)}. 829 | t_any(L) -> {type, L, any, []}. 830 | t_atom(L) -> {type, L, atom, []}. 831 | t_atom(L, A) -> {atom, L, A}. 832 | t_integer(L) -> {type, L, integer, []}. 833 | t_integer(L, I) -> {integer, L, I}. 834 | t_list(L, Es) -> {type, L, list, Es}. 835 | t_fun(L, As, Res) -> {type, L, 'fun', [{type, L, product, As}, Res]}. 836 | t_tuple(L, Es) -> {type, L, tuple, Es}. 837 | t_boolean(L) -> {type, L, boolean, []}. 838 | t_record(L, A) -> {type, L, record, [{atom, L, A}]}. 839 | 840 | f_set_2(Rname, Flds, L, Acc) -> 841 | Fname = fname(set, Rname, Acc), 842 | TRec = t_record(L, Rname), 843 | [funspec(L, Fname, [t_list(L, [t_prop(L, Rname, Acc)]), TRec], TRec), 844 | {function, L, Fname, 2, 845 | [{clause, L, [{var, L, 'Vals'}, {var, L, 'Rec'}], [], 846 | [{match, L, {var, L, 'F'}, 847 | {'fun', L, 848 | {clauses, 849 | [{clause, L, [{nil,L}, 850 | {var,L,'R'}, 851 | {var,L,'_F1'}], 852 | [], 853 | [{var, L, 'R'}]} | 854 | [{clause, L, 855 | [{cons, L, {tuple, L, [{atom, L, Attr}, 856 | {var, L, 'V'}]}, 857 | {var, L, 'T'}}, 858 | {var, L, 'R'}, 859 | {var, L, 'F1'}], 860 | [[{call, L, {atom, L, is_list}, [{var, L, 'T'}]}]], 861 | [{call, L, {var, L, 'F1'}, 862 | [{var,L,'T'}, 863 | {record, L, {var,L,'R'}, Rname, 864 | [{record_field, L, 865 | {atom, L, Attr}, 866 | {var, L, 'V'}}]}, 867 | {var, L, 'F1'}]}]} || Attr <- Flds] 868 | ++ [{clause, L, [{var, L, 'Vs'}, {var,L,'R'},{var,L,'_'}], 869 | [], 870 | [bad_record_op(L, Fname, 'Vs', 'R')]}] 871 | ]}}}, 872 | {call, L, {var, L, 'F'}, [{var, L, 'Vals'}, 873 | {var, L, 'Rec'}, 874 | {var, L, 'F'}]}]}]}]. 875 | 876 | bad_record_op(L, Fname, Val) -> 877 | {call, L, {remote, L, {atom,L,erlang}, {atom,L,error}}, 878 | [{atom,L,bad_record_op}, {cons, L, {atom, L, Fname}, 879 | {cons, L, {var, L, Val}, 880 | {nil, L}}}]}. 881 | 882 | bad_record_op(L, Fname, Val, R) -> 883 | {call, L, {remote, L, {atom,L,erlang}, {atom,L,error}}, 884 | [{atom,L,bad_record_op}, {cons, L, {atom, L, Fname}, 885 | {cons, L, {var, L, Val}, 886 | {cons, L, {var, L, R}, 887 | {nil, L}}}}]}. 888 | 889 | 890 | f_pos_1(Rname, Flds, L, Acc) -> 891 | Fname = fname(pos, Rname, Acc), 892 | FieldList = lists:zip(Flds, lists:seq(2, length(Flds)+1)), 893 | [ 894 | funspec(L, Fname, [t_union(L, [t_attr(L, Rname, Acc), 895 | t_atom(L)])], 896 | t_integer(L)), 897 | {function, L, Fname, 1, 898 | [{clause, L, 899 | [{atom, L, FldName}], 900 | [], 901 | [{integer, L, Pos}]} || {FldName, Pos} <- FieldList] ++ 902 | [{clause, L, 903 | [{var, L, 'A'}], 904 | [[{call, L, {atom, L, is_atom}, [{var, L, 'A'}]}]], 905 | [{integer, L, 0}]}] 906 | }]. 907 | 908 | f_fromlist_1(Rname, L, Acc) -> 909 | Fname = fname(fromlist, Rname, Acc), 910 | [ 911 | funspec(L, Fname, [t_list(L, [t_prop(L, Rname, Acc)])], 912 | t_record(L, Rname)), 913 | {function, L, Fname, 1, 914 | [{clause, L, [{var, L, 'Vals'}], 915 | [[ {call, L, {atom, L, is_list}, [{var, L, 'Vals'}]} ]], 916 | [{call, L, {atom, L, Fname}, 917 | [{var, L, 'Vals'}, 918 | {call, L, {atom, L, fname(new, Rname, Acc)}, []}]} 919 | ]} 920 | ]}]. 921 | 922 | f_fromlist_2(Rname, Flds, L, Acc) -> 923 | Fname = fname(fromlist, Rname, Acc), 924 | FldList = field_list(Flds), 925 | TRec = t_record(L, Rname), 926 | [ 927 | funspec(L, Fname, [t_list(L, [t_prop(L, Rname, Acc)]), TRec], 928 | TRec), 929 | {function, L, Fname, 2, 930 | [{clause, L, [{var, L, 'Vals'}, {var, L, 'Rec'}], [], 931 | [{match, L, {var, L, 'AttrNames'}, FldList}, 932 | {match, L, {var, L, 'F'}, 933 | {'fun', L, 934 | {clauses, 935 | [{clause, L, [{nil, L}, 936 | {var, L,'R'}, 937 | {var, L,'_F1'}], 938 | [], 939 | [{var, L, 'R'}]}, 940 | {clause, L, [{cons, L, 941 | {tuple, L, [{var, L, 'H'}, 942 | {var, L, 'Pos'}]}, 943 | {var, L, 'T'}}, 944 | {var, L, 'R'}, {var, L, 'F1'}], 945 | [[{call, L, {atom, L, is_list}, [{var, L, 'T'}]}]], 946 | [{'case', L, {call, L, {remote, L, 947 | {atom,L,lists},{atom,L,keyfind}}, 948 | [{var,L,'H'},{integer,L,1},{var,L,'Vals'}]}, 949 | [{clause, L, [{atom,L,false}], [], 950 | [{call, L, {var, L, 'F1'}, [{var, L, 'T'}, 951 | {var, L, 'R'}, 952 | {var, L, 'F1'}]}]}, 953 | {clause, L, [{tuple, L, [{var,L,'_'},{var,L,'Val'}]}], 954 | [], 955 | [{call, L, {var, L, 'F1'}, 956 | [{var, L, 'T'}, 957 | {call, L, {atom, L, 'setelement'}, 958 | [{var, L, 'Pos'}, {var, L, 'R'}, {var, L, 'Val'}]}, 959 | {var, L, 'F1'}]}]} 960 | ]} 961 | ]} 962 | ]}}}, 963 | {call, L, {var, L, 'F'}, [{var, L, 'AttrNames'}, 964 | {var, L, 'Rec'}, 965 | {var, L, 'F'}]} 966 | ]} 967 | ]}]. 968 | 969 | field_list(Flds) -> 970 | erl_parse:abstract( 971 | lists:zip(Flds, lists:seq(2, length(Flds)+1))). 972 | 973 | 974 | 975 | f_get_2(R, Flds, L, Acc) -> 976 | FName = fname(get, R, Acc), 977 | {_, Types} = lists:keyfind(R, 1, Acc#pass1.record_types), 978 | [funspec(L, FName, 979 | [{[t_atom(L, A), t_record(L, R)], T} 980 | || {A, T} <- Types] 981 | ++ [{[t_list(L, [t_attr(L, R, Acc)]), t_record(L, R)], 982 | t_list(L, [t_union(L, [Ts || {_, Ts} <- Types])])}] 983 | ), 984 | {function, L, FName, 2, 985 | [{clause, L, [{var, L, 'Attrs'}, {var, L, 'R'}], 986 | [[{call, L, {atom, L, is_list}, [{var, L, 'Attrs'}]}]], 987 | [{lc, L, {call, L, {atom, L, FName}, [{var, L, 'A'}, {var, L, 'R'}]}, 988 | [{generate, L, {var, L, 'A'}, {var, L, 'Attrs'}}]}] 989 | } | 990 | [{clause, L, [{atom, L, Attr}, {var, L, 'R'}], [], 991 | [{record_field, L, {var, L, 'R'}, R, {atom, L, Attr}}]} || 992 | Attr <- Flds]] ++ 993 | [{clause, L, [{var, L, 'Attr'}, {var, L, 'R'}], [], 994 | [bad_record_op(L, FName, 'Attr', 'R')]}] 995 | }]. 996 | 997 | 998 | f_info(Acc, L) -> 999 | Fname = list_to_atom(fname_prefix(info, Acc)), 1000 | [funspec(L, Fname, 1001 | [{[t_atom(L, R)], 1002 | t_list(L, [t_union(L, [t_atom(L,A) || 1003 | A <- get_flds(R, Acc)])])} 1004 | || R <- Acc#pass1.exports]), 1005 | {function, L, Fname, 1, 1006 | [{clause, L, 1007 | [{var, L, 'RecName'}], [], 1008 | [{call, L, {atom, L, Fname}, [{var, L, 'RecName'}, {atom, L, fields}]}] 1009 | }]} 1010 | ]. 1011 | 1012 | f_isrec_2(#pass1{records = Rs, exports = Es} = Acc, L) -> 1013 | Fname = list_to_atom(fname_prefix(is_record, Acc)), 1014 | Info = [{R,length(As) + 1} || {R,As} <- Rs, lists:member(R, Es)], 1015 | [%% This contract is correct, but is ignored by Dialyzer because it 1016 | %% has overlapping domains: 1017 | %% funspec(L, Fname, 1018 | %% [{[t_atom(L, R), t_record(L, R)], t_atom(L, true)} 1019 | %% || R <- Es] ++ 1020 | %% [{[t_any(L), t_any(L)], t_atom(L, false)}]), 1021 | %% This is less specific, but more useful to Dialyzer: 1022 | funspec(L, Fname, [{[t_any(L), t_any(L)], t_boolean(L)}]), 1023 | {function, L, Fname, 2, 1024 | lists:map( 1025 | fun({R, Ln}) -> 1026 | {clause, L, 1027 | [{atom, L, R}, {var, L, 'Rec'}], 1028 | [[{op,L,'==', 1029 | {call, L, {atom,L,tuple_size},[{var,L,'Rec'}]}, 1030 | {integer, L, Ln}}, 1031 | {op,L,'==', 1032 | {call,L,{atom,L,element},[{integer,L,1}, 1033 | {var,L,'Rec'}]}, 1034 | {atom, L, R}}]], 1035 | [{atom, L, true}]} 1036 | end, Info) ++ 1037 | [{clause, L, [{var,L,'_'}, {var,L,'_'}], [], 1038 | [{atom, L, false}]}]} 1039 | ]. 1040 | 1041 | 1042 | f_info_2(Acc, L) -> 1043 | Fname = list_to_atom(fname_prefix(info, Acc)), 1044 | [funspec(L, Fname, 1045 | lists:flatmap( 1046 | fun(Rname) -> 1047 | Flds = get_flds(Rname, Acc), 1048 | TRec = t_atom(L, Rname), 1049 | [{[TRec, t_atom(L, size)], t_integer(L, length(Flds)+1)}, 1050 | {[TRec, t_atom(L, fields)], 1051 | t_list(L, [t_union(L, [t_atom(L, F) || F <- Flds])])}] 1052 | end, Acc#pass1.exports)), 1053 | {function, L, Fname, 2, 1054 | [{clause, L, 1055 | [{atom, L, R}, 1056 | {var, L, 'Info'}], 1057 | [], 1058 | [{call, L, {atom, L, fname(info, R, Acc)}, [{var, L, 'Info'}]}]} || 1059 | R <- Acc#pass1.exports]} 1060 | ]. 1061 | 1062 | f_info_3(Versions, L, Acc) -> 1063 | Fname = list_to_atom(fname_prefix(info, Acc)), 1064 | [ 1065 | {function, L, Fname, 3, 1066 | [{clause, L, 1067 | [{atom, L, R}, 1068 | {var, L, 'Info'}, 1069 | {string, L, V}], 1070 | [], 1071 | [{call, L, {atom, L, fname(info,R,V,Acc)}, [{var, L, 'Info'}]}]} || 1072 | {R,V} <- flat_versions(Versions)]} 1073 | ]. 1074 | 1075 | f_pos_2(#pass1{exports = Es} = Acc, L) -> 1076 | Fname = list_to_atom(fname_prefix(pos, Acc)), 1077 | [ 1078 | funspec(L, Fname, lists:flatmap( 1079 | fun(R) -> 1080 | Flds = get_flds(R, Acc), 1081 | PFlds = lists:zip( 1082 | lists:seq(1, length(Flds)), Flds), 1083 | [{[t_atom(L, R), t_atom(L, A)], 1084 | t_integer(L, P)} || {P,A} <- PFlds] 1085 | end, Es)), 1086 | {function, L, Fname, 2, 1087 | [{clause, L, 1088 | [{atom, L, R}, 1089 | {var, L, 'Attr'}], 1090 | [], 1091 | [{call, L, {atom, L, fname(pos, R, Acc)}, [{var, L, 'Attr'}]}]} || 1092 | R <- Acc#pass1.exports]} 1093 | ]. 1094 | 1095 | f_isrec_1(Acc, L) -> 1096 | Fname = list_to_atom(fname_prefix(is_record, Acc)), 1097 | [%% This contract is correct, but is ignored by Dialyzer because it 1098 | %% has overlapping domains: 1099 | %% funspec(L, Fname, 1100 | %% [{[t_record(L, R)], t_atom(L, true)} 1101 | %% || R <- Acc#pass1.exports] 1102 | %% ++ [{[t_any(L)], t_atom(L, false)}]), 1103 | %% This is less specific, but more useful to Dialyzer: 1104 | funspec(L, Fname, [{[t_any(L)], t_boolean(L)}]), 1105 | {function, L, Fname, 1, 1106 | [{clause, L, 1107 | [{var, L, 'X'}], 1108 | [], 1109 | [{'if',L, 1110 | [{clause, L, [], [[{call, L, {atom,L,is_record}, 1111 | [{var,L,'X'},{atom,L,R}]}]], 1112 | [{atom,L,true}]} || R <- Acc#pass1.exports] ++ 1113 | [{clause,L, [], [[{atom,L,true}]], 1114 | [{atom, L, false}]}]}]} 1115 | ]} 1116 | ]. 1117 | 1118 | 1119 | 1120 | f_get(#pass1{record_types = RTypes, exports = Es} = Acc, L) -> 1121 | Fname = list_to_atom(fname_prefix(get, Acc)), 1122 | [funspec(L, Fname, 1123 | lists:append( 1124 | [[{[t_atom(L, A), t_record(L, R)], T} 1125 | || {A, T} <- Types] 1126 | || {R, Types} <- RTypes, lists:member(R, Es)]) 1127 | ++ [{[t_list(L, [t_attr(L, R, Acc)]), t_record(L, R)], 1128 | t_list(L, [t_union(L, [Ts || {_, Ts} <- Types])])} 1129 | || {R, Types} <- RTypes, lists:member(R, Es)] 1130 | ), 1131 | {function, L, Fname, 2, 1132 | [{clause, L, 1133 | [{var, L, 'Attrs'}, 1134 | {var, L, 'Rec'}], 1135 | [[{call, L, 1136 | {atom, L, is_record}, 1137 | [{var, L, 'Rec'}, {atom, L, R}]}]], 1138 | [{call, L, {atom, L, fname(get, R, Acc)}, [{var, L, 'Attrs'}, 1139 | {var, L, 'Rec'}]}]} || 1140 | R <- Es]} 1141 | ]. 1142 | 1143 | 1144 | f_set(Acc, L) -> 1145 | Fname = list_to_atom(fname_prefix(set, Acc)), 1146 | [funspec(L, Fname, 1147 | lists:map( 1148 | fun(Rname) -> 1149 | TRec = t_record(L, Rname), 1150 | {[t_list(L, [t_prop(L, Rname, Acc)]), TRec], TRec} 1151 | end, Acc#pass1.exports)), 1152 | {function, L, Fname, 2, 1153 | [{clause, L, 1154 | [{var, L, 'Vals'}, 1155 | {var, L, 'Rec'}], 1156 | [[{call, L, 1157 | {atom, L, is_record}, 1158 | [{var, L, 'Rec'}, {atom, L, R}]}]], 1159 | [{call, L, {atom, L, fname(set, R, Acc)}, [{var, L, 'Vals'}, 1160 | {var, L, 'Rec'}]}]} || 1161 | R <- Acc#pass1.exports]} 1162 | ]. 1163 | 1164 | f_fromlist(Acc, L) -> 1165 | Fname = list_to_atom(fname_prefix(fromlist, Acc)), 1166 | [funspec(L, Fname, 1167 | lists:map( 1168 | fun(Rname) -> 1169 | TRec = t_record(L, Rname), 1170 | {[t_list(L, [t_prop(L, Rname, Acc)]), TRec], TRec} 1171 | end, Acc#pass1.exports)), 1172 | {function, L, Fname, 2, 1173 | [{clause, L, 1174 | [{var, L, 'Vals'}, 1175 | {var, L, 'Rec'}], 1176 | [[{call, L, 1177 | {atom, L, is_record}, 1178 | [{var, L, 'Rec'}, {atom, L, R}]}]], 1179 | [{call, L, {atom, L, fname(fromlist, R, Acc)}, [{var, L, 'Vals'}, 1180 | {var, L, 'Rec'}]}]} || 1181 | R <- Acc#pass1.exports]} 1182 | ]. 1183 | 1184 | f_info_1(Rname, Acc, L) -> 1185 | Fname = fname(info, Rname, Acc), 1186 | Flds = get_flds(Rname, Acc), 1187 | [funspec(L, Fname, [{[t_atom(L, fields)], 1188 | t_list(L, [t_union(L, [t_atom(L,F) || F <- Flds])])}, 1189 | {[t_atom(L, size)], t_integer(L, length(Flds))}]), 1190 | {function, L, Fname, 1, 1191 | [{clause, L, [{atom, L, fields}], [], 1192 | [{call, L, {atom, L, record_info}, 1193 | [{atom, L, fields}, {atom, L, Rname}]}] 1194 | }, 1195 | {clause, L, [{atom, L, size}], [], 1196 | [{call, L, {atom, L, record_info}, 1197 | [{atom, L, size}, {atom, L, Rname}]}] 1198 | }]} 1199 | ]. 1200 | 1201 | f_info_1(Rname, Acc, L, V) -> 1202 | f_info_1(recname(Rname, V), Acc, L). 1203 | 1204 | recname(Rname, V) -> 1205 | list_to_atom(lists:concat([Rname,"__",V])). 1206 | 1207 | f_convert(_Vsns, L, Acc) -> 1208 | {function, L, fname(convert, Acc), 2, 1209 | [{clause, L, 1210 | [{var, L, 'FromVsn'}, 1211 | {var, L, 'Rec'}], 1212 | [[{call,L,{atom, L, is_tuple}, 1213 | [{var, L, 'Rec'}]}]], 1214 | [{match, L, {var, L, 'Rname'}, 1215 | {call, L, {atom, L, element}, 1216 | [{integer, L, 1}, {var, 1, 'Rec'}]}}, 1217 | {match,L,{var,L,'Size'}, 1218 | {call, L, {atom, L, fname(info, Acc)}, 1219 | [{var,L,'Rname'}, {atom, L, size}, {var,L,'FromVsn'}]}}, 1220 | {match, L, {var, L, 'Size'}, 1221 | {call, L, {atom, L, size}, 1222 | [{var, L, 'Rec'}]}}, 1223 | %% 1224 | %% {match, L, {var, L, 'Old_fields'}, 1225 | %% {call, L, {atom,L,fname(info, Acc)}, 1226 | %% [{var,L,'Rname'},{atom,L,fields},{var,L,'FromVsn'}]}}, 1227 | {match, L, {var, L, 'New_fields'}, 1228 | {call, L, {atom,L,fname(info, Acc)}, 1229 | [{var,L,'Rname'},{atom,L,fields}]}}, 1230 | 1231 | {match, L, {var, L, 'Values'}, 1232 | {call, L, {remote, L, {atom, L, lists}, {atom, L, zip}}, 1233 | [{call, L, {atom,L,fname(info, Acc)}, 1234 | [{var,L,'Rname'},{atom,L,fields},{var,L,'FromVsn'}]}, 1235 | {call, L, {atom, L, 'tl'}, 1236 | [{call, L, {atom, L, tuple_to_list}, 1237 | [{var, L, 'Rec'}]}]}]}}, 1238 | {match, L, {tuple, L, [{var, L, 'Matching'}, 1239 | {var, L, 'Discarded'}]}, 1240 | {call, L, {remote, L, {atom, L, lists}, {atom, L, partition}}, 1241 | [{'fun',L, 1242 | {clauses, 1243 | [{clause,L, 1244 | [{tuple,L,[{var,L,'F'},{var,L,'_'}]}], 1245 | [], 1246 | [{call,L, 1247 | {remote,L,{atom,L,lists},{atom,L,member}}, 1248 | [{var, L, 'F'}, {var,L,'New_fields'}]}]}]}}, 1249 | {var, L, 'Values'}]}}, 1250 | {tuple, L, [{call, L, {atom, L, fname(set, Acc)}, 1251 | [{var, L, 'Matching'}, 1252 | {call, L, {atom, L, fname(new, Acc)}, 1253 | [{var, L, 'Rname'}]}]}, 1254 | {var, L, 'Discarded'}]}] 1255 | }]}. 1256 | 1257 | f_lens_(#pass1{exports = Es} = Acc, L) -> 1258 | Fname = fname(lens, Acc), 1259 | [ 1260 | funspec(L, Fname, [ {[t_attr(L, Rname, Acc), t_atom(L, Rname)], 1261 | t_tuple(L, [t_fun(L, [t_record(L, Rname)], t_any(L)), 1262 | t_fun(L, [t_any(L), 1263 | t_record(L, Rname)], 1264 | t_record(L, Rname))])} 1265 | || Rname <- Es]), 1266 | {function, L, Fname, 2, 1267 | [{clause, L, [{var, L, 'Attr'}, {atom, L, Re}], [], 1268 | [{call, L, {atom, L, fname(lens, Re, Acc)}, [{var, L, 'Attr'}]}]} 1269 | || Re <- Es]} 1270 | ]. 1271 | 1272 | f_lens_1(Rname, Flds, L, Acc) -> 1273 | Fname = fname(lens, Rname, Acc), 1274 | [funspec(L, Fname, [ {[t_attr(L, Rname, Acc)], 1275 | t_tuple(L, [t_fun(L, [t_record(L, Rname)], t_any(L)), 1276 | t_fun(L, [t_any(L), 1277 | t_record(L, Rname)], 1278 | t_record(L, Rname))])} ]), 1279 | {function, L, Fname, 1, 1280 | [{clause, L, [{atom, L, Attr}], [], 1281 | [{tuple, L, [{'fun', L, 1282 | {clauses, 1283 | [{clause, L, [{var, L, 'R'}], [], 1284 | [{call, L, {atom, L, fname(get, Rname, Acc)}, 1285 | [{atom, L, Attr}, {var, L, 'R'}]}]} 1286 | ]}}, 1287 | {'fun', L, 1288 | {clauses, 1289 | [{clause, L, [{var, L, 'X'}, {var, L, 'R'}], [], 1290 | [{call, L, {atom, L, fname(set, Rname, Acc)}, 1291 | [{cons,L, {tuple, L, [{atom, L, Attr}, 1292 | {var, L, 'X'}]}, {nil,L}}, 1293 | {var, L, 'R'}]}] 1294 | }]}} 1295 | ]}]} || Attr <- Flds] ++ 1296 | [{clause, L, [{var, L, 'Attr'}], [], 1297 | [bad_record_op(L, Fname, 'Attr')]}] 1298 | }]. 1299 | 1300 | %%% ========== generic parse_transform stuff ============== 1301 | 1302 | -spec context(atom(), #context{}) -> 1303 | term(). 1304 | %% @hidden 1305 | context(module, #context{module = M} ) -> M; 1306 | context(function, #context{function = F}) -> F; 1307 | context(arity, #context{arity = A} ) -> A. 1308 | 1309 | 1310 | 1311 | rpt_error(Reason, Fun, Info) -> 1312 | Fmt = lists:flatten( 1313 | ["*** ERROR in parse_transform function:~n" 1314 | "*** Reason = ~p~n", 1315 | "*** Location: ~p~n", 1316 | ["*** ~10w = ~p~n" || _ <- Info]]), 1317 | Args = [Reason, Fun | 1318 | lists:foldr( 1319 | fun({K,V}, Acc) -> 1320 | [K, V | Acc] 1321 | end, [], Info)], 1322 | io:format(Fmt, Args). 1323 | 1324 | -spec format_error({atom(), term()}) -> 1325 | iolist(). 1326 | %% @hidden 1327 | format_error({_Cat, Error}) -> 1328 | Error. 1329 | --------------------------------------------------------------------------------