├── 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 |
| ct_expand |
| exprecs |
| parse_trans |
| parse_trans_codegen |
| parse_trans_mod |
| parse_trans_pp |
| ct_expand |
| exprecs |
| parse_trans |
| parse_trans_codegen |
| parse_trans_mod |
| parse_trans_pp |
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/2 | Pretty-prints the erlang source code corresponding to Forms into Out. |
main(X1::[string()]) -> any()50 |
pp_beam(Beam::filename()) -> string() | {error, Reason}
61 | pp_beam(Beam::filename(), Out::filename()) -> ok | {error, Reason}
76 | pp_src(Forms0::Forms, Out::filename()) -> ok91 |
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 |
parse_transform(Forms::forms(), Options::options()) -> forms()104 |
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 |
abstract_code(BeamFile::binary()) -> erlang_form()52 |
beam_file(Module::module()) -> binary()63 |
compile_and_load_forms(AbsCode::erlang_form()) -> ok74 |
compile_and_load_forms(AbsCode::erlang_form(), Opts::compile_options()) -> ok85 |
compile_options(BeamFile::binary() | module()) -> compile_options()96 |
rename_module(T::erlang_form(), NewName::module()) -> erlang_form()107 |
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. |
parse_transform(Forms, Options) -> NewForms51 |
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
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 |
newgetsetfromlistinfoposis_recordconvertpropattrlensform() = 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 |
parse_transform(Forms::forms(), Options::options()) -> forms()500 |
...
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 | %%
66 | %% f(Name, L) ->
67 | %% codegen:gen_function(
68 | %% Name,
69 | %% [ fun({'$var',X}) ->
70 | %% {'$var', Y}
71 | %% end || {X, Y} <- 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 | %%
{'$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 | %% {'$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', 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/2 | Equivalent to format_exception(Class, Reason, 4). |
| format_exception/3 | Produces 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/2 | Pretty-prints the erlang source code corresponding to Forms into Out. |
| replace_function/4 | |
| return/2 | Checks the transformed result for errors and warnings. |
| revert/1 | Reverts back from Syntax Tools format to Erlang forms. |
| revert_form/1 | Reverts a single form back from Syntax Tools format to Erlang forms. |
| top/3 | |
| transform/4 | 100 | Makes one pass. |
context(X1::Attr, Context) -> any()115 |
Attr = module | function | arity | options
depth_first(Fun::xform_f_df(), Acc, Forms::forms(), Options::options()) -> {forms(), Acc} | {error, list()}
128 | do_depth_first(F::xform_f_df(), Acc::term(), Forms::forms(), Context::#context{}) -> {forms(), term()}
139 | do_insert_forms(X1::above | below, Insert::forms(), Forms::forms(), Context::#context{}) -> forms()
150 | do_inspect(F::insp_f(), Acc::term(), Forms::forms(), Context::#context{}) -> term()
161 | do_transform(F::xform_f_rec(), Acc::term(), Forms::forms(), Context::#context{}) -> {forms(), term()}
172 | error(R::Reason, F::Form, I::Info) -> throw()183 |
Info = [{Key, Value}]format_error(Error::{atom(), term()}) -> iolist()
207 | format_exception(Class, Reason) -> String218 |
format_exception(Class, Reason, Lines) -> String231 |
Class = error | throw | exit
Reason = term()
Lines = integer() | infinity
function_exists(Fname::atom(), Arity::integer(), Forms) -> boolean()251 |
get_attribute(A, Forms) -> any()265 |
A = atom()
get_file(Forms) -> string()287 |
get_module(Forms) -> atom()301 |
get_orig_syntax_tree(File) -> Forms315 |
get_pos(I::list()) -> integer()334 |
initial_context(Forms, Options) -> #context{}
349 | inspect(F::Fun, Acc::Forms, Forms::Acc, Options) -> NewAcc366 |
Fun = function()
optionally_pretty_print(Result::forms(), Options::options(), Context::#context{}) -> ok
379 | plain_transform(Fun, Forms) -> forms()390 |
Fun = function()
Forms = forms()
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 | pp_beam(Beam::filename(), Out::filename()) -> ok | {error, Reason}
443 | pp_src(Res::Forms, Out::filename()) -> ok458 |
return(Forms, Context) -> Forms | {error, Es, Ws} | {warnings, Forms, Ws}
481 | revert(Tree) -> Forms517 |
revert_form(F::Tree) -> Form543 |
top(F::function(), Forms::forms(), Options::list()) -> forms() | {error, term()}
574 | transform(Fun, Acc, Forms, Options) -> {TransformedForms, NewAcc}
585 | Fun = function()
Options = [{Key, Value}]...
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 | %%%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 WigerThis 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:
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 | %%