Permalink
Browse files

initial vsn githubified and rebarified

  • Loading branch information...
0 parents commit 68435f850d64c686dc4e6434ab2b872e9bbe0629 Ulf Wiger committed Sep 16, 2010
Showing with 1,499 additions and 0 deletions.
  1. +178 −0 LICENSE
  2. +31 −0 Makefile
  3. +7 −0 NOTICE
  4. +158 −0 README.md
  5. +27 −0 include/plain_fsm.hrl
  6. BIN rebar
  7. +6 −0 rebar.config
  8. +110 −0 src/fsm_example.erl
  9. +28 −0 src/plain_fsm.app.src
  10. +636 −0 src/plain_fsm.erl
  11. +318 −0 src/plain_fsm_xform.erl
178 LICENSE
@@ -0,0 +1,178 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
31 Makefile
@@ -0,0 +1,31 @@
+##==============================================================================
+## Copyright 2010 Erlang Solutions Ltd.
+##
+## Licensed under the Apache License, Version 2.0 (the "License");
+## you may not use this file except in compliance with the License.
+## You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+##==============================================================================
+.PHONY: rel all clean
+
+all: compile
+
+compile:
+ ./rebar compile
+
+clean:
+ ./rebar clean
+
+test:
+ ./rebar eunit
+
+doc:
+ ./rebar doc
+
7 NOTICE
@@ -0,0 +1,7 @@
+Plain_fsm, copyright 2010 Erlang Solutions
+
+This product contains code developed at Erlang Solutions.
+(http://www.erlang-solutions.com/)
+
+The original was implemented by Ulf Wiger while at Ericsson AB,
+and released under the EPL license as part of http://jungerl.sf.net/
158 README.md
@@ -0,0 +1,158 @@
+A behaviour/support library for writing plain Erlang FSMs.
+==========================================================
+
+This module implements an OTP behaviour for writing plain Erlang FSMs,
+alleviating a long-standing gripe of mine that the OTP behaviours, for all
+their power, force programmers into a coding style that is very much
+different from that taught in the Basic Erlang Course (or the book, or
+online tutorials, ...) -- the type of programming that made us want to
+use Erlang in the first place.
+
+The requirements that drove us away from plain Erlang programming
+in the first place were:
+
+- The need to support *system messages* to control upgrade,
+ state inspection, shutdown, etc. The `plain_fsm` library solves this
+ in a reasonable way, I think.
+- The need for debugging support. The debugging support in
+ e.g. `gen_server` is, I believe, rendered obsolete by the new powerful
+ trace support (and `dbg`) in later versions of OTP.
+- In the case of gen_server, reducing the need to reinvent the
+ wheel, a valid point, but more so for e.g. the client side of
+ gen_server:call(). In a protocol state machine, the only thing that
+ really needs reusing is the handling of system messages.
+
+
+However, the behaviours provided by OTP for FSM programming,
+`gen_server` and `gen_fsm` (`gen_server` is perhaps a more common
+choice than `gen_fsm`), both have the distinct drawback that you
+cannot normally start with a classic Erlang design and then migrate
+to a behaviour without significant code rewrite. In addition, the
+two behaviours are semantically different from the classic Erlang design
+
+Using plain_fsm
+---------------
+
+First, write your state machine without worrying about OTP system
+messages. Once you're happy with it, figure out where you really want
+to handle system messages. Normally, it will suffice to do it in a fairly
+stable state. A good rule of thumb is that the top-level state machine
+should handle system messages, while the transient (sub-) states
+shouldn't
+
+In the states where you want to handle system messages, you have
+three choices:
+
+### (A) Insert the system messages in the receive clause:#
+
+ idle(S) ->
+ Parent = plain_fsm:info(parent),
+ receive
+ {system, From, Req} ->
+ plain_fsm:handle_system_msg(
+ From, Req, S, fun(S1) -> idle(S1) end);
+ {'EXIT', Parent, Reason} ->
+ plain_fsm:parent_EXIT(Reason, S);
+ ... %% your original code here
+ end.
+
+This has the advantage that everyone can understand what's going on.
+The part that plain_fsm.erl helps you with is the set of functions
+`system_code_change()`, `system_continue()`, `code>system_shutdown()`,
+`format_status()`, which are required callbacks when you handle system
+messages directly.
+
+### (B) Handle system messages and unknown messages together: #
+
+ idle(S) ->
+ Parent = plain_fsm:info(parent),
+ receive
+ ... %% your original code here
+ Msg ->
+ plain_fsm:handle_msg(Msg, State, fun(S1) -> idle(S1) end)
+ end.
+
+This is quite convenient if the receive statement already has a
+'catch-all' clause, discarding unknown messages.
+`plain_fsm:handle_msg/3` will handle system messages properly
+and ignore any other message.
+
+### (C) Write a pseudo wrapper function around your receive clause:
+
+
+ idle(S) ->
+ plain_fsm:extended_receive(
+ receive
+ ... %% your original code
+ end).
+
+The function `plain_fsm:extended_receive/1` is replaced
+in a *parse_transform* into something that looks very much like
+the previous program (A). The code, as it reads, requires the reader to
+know that the transformation takes place, otherwise the semantics
+would be confusing (you cannot solve the problem using a real function
+that way.) On the plus side, this is a fairly small violation of both
+the original code and Erlang's semantics.
+
+*Note that for this to work, you must include "plain_fsm.hrl"
+in your module.*
+
+#### Example #
+
+In the module fsm_example.erl (included in the plain_fsm package),
+we choose to handle system messages in the idle state. The example
+code is runnable, and supports suspend, resume, status inspection,
+and code change.
+
+Imagine that the code initially looked like this:
+
+ idle(S) ->
+ receive
+ a ->
+ io:format("going to state a~n", []),
+ a(S);
+ b ->
+ io:format("going to state b~n", []),
+ b(S)
+ after 10000 ->
+ io:format("timeout in idle~n", []),
+ idle(S)
+ end).
+
+The change required to handle system messages is as follows:
+
+ idle(S) ->
+ plain_fsm:extended_receive(
+ receive
+ a ->
+ io:format("going to state a~n", []),
+ a(S);
+ b ->
+ io:format("going to state b~n", []),
+ b(S)
+ after 10000 ->
+ io:format("timeout in idle~n", []),
+ idle(S)
+ end).
+
+In addition, we change the start function from, in this case:
+
+ spawn_link() ->
+ spawn_link(fun() ->
+ process_flag(trap_exit, true),
+ idle(mystate)
+ end).
+
+Is changed into:
+
+ spawn_link() ->
+ plain_fsm:spawn_link(?MODULE, fun() ->
+ process_flag(trap_exit,true),
+ idle(mystate)
+ end).
+
+See also spawn/2 and spawn_opt/3 for information on other possible
+start functions.
+
+To be fully compliant, you also need to supply a `code_change/3` function.
+See behaviour_info/1 for details.
27 include/plain_fsm.hrl
@@ -0,0 +1,27 @@
+%%==============================================================================
+%% Copyright 2010 Erlang Solutions Ltd.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%==============================================================================
+
+%%-------------------------------------------------------------------
+%% File : plain_fsm.hrl
+%% Author : Ulf Wiger <ulf.wiger@ericsson.com>
+%% Description :
+%%
+%% Created : 29 Jan 2004 by Ulf Wiger <ulf.wiger@ericsson.com>
+%%-------------------------------------------------------------------
+
+-compile({parse_transform, plain_fsm_xform}).
+
+
BIN rebar
Binary file not shown.
6 rebar.config
@@ -0,0 +1,6 @@
+{erl_opts, [fail_on_warning, debug_info]}.
+{erl_first_files, ["src/plain_fsm_xform.erl","src/plain_fsm.erl"]}.
+{xref_checks, [undefined_function_calls]}.
+
+{cover_enabled, true}.
+{clean_files, [".eunit", "ebin/*.beam","*~","*/*~"]}.
110 src/fsm_example.erl
@@ -0,0 +1,110 @@
+%%==============================================================================
+%% Copyright 2010 Erlang Solutions Ltd.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%==============================================================================
+
+%%-------------------------------------------------------------------
+%% File : fsm_example.erl
+%% Author : Ulf Wiger <ulf.wiger@ericsson.com>
+%% Description : Example illustrating the use of
+%%
+%% Created : 28 Jan 2004 by Ulf Wiger <ulf.wiger@ericsson.com>
+%%-------------------------------------------------------------------
+-module(fsm_example).
+-behaviour(plain_fsm).
+
+%% Here's the plan:
+%% plain_fsm.erl will include behaviour_info(),
+%% the exported function plain_fsm:extended_receive/1
+%% (which should never actually be called -- it will simply exit),
+%% and the support functions spawn_link(), system_continue(),
+%% system_code_change() et al.
+%%
+%% Users of plain_fsm must abide by a few rules:
+%%
+%% - at least one receive clause somewhere should be wrapped
+%% inside a plain_fsm:extended_receive(receive ... end).
+%% This will ensure that system messages are handled, including
+%% the shutdown protocol, without giving up selective receive.
+%% - The function containing the extended_receive wrapper should
+%% have exactly one argument -- the 'State'.
+%%
+-export([spawn_link/0]).
+-export([data_vsn/0, code_change/3]).
+-include("plain_fsm.hrl").
+
+%% needed because they are called via hibernate()
+-export([a/1, b/1]).
+
+data_vsn() ->
+ 5.
+
+spawn_link() ->
+ plain_fsm:spawn_link(?MODULE, fun() ->
+ process_flag(trap_exit,true),
+ idle(mystate)
+ end).
+
+
+
+idle(S) ->
+ %% Pseudo-calls to plain_fsm:extended_receive can only occur in
+ %% functions with arity 1 (the single argument being used as the state.)
+ plain_fsm:extended_receive(
+ receive
+ a ->
+ io:format("going to state a~n", []),
+ plain_fsm:hibernate(?MODULE,a,[S]);
+ b ->
+ io:format("going to state b~n", []),
+ b(S)
+ after 10000 ->
+ io:format("timeout in idle~n", []),
+ idle(S)
+ end).
+
+
+a(S) ->
+ receive
+ b ->
+ io:format("going to state b~n", []),
+ eventually_b(S);
+ idle ->
+ io:format("going to state idle~n", []),
+ idle(S)
+% after 10000 ->
+% io:format("timeout in a~n", []),
+% idle(S)
+ end.
+
+b(S) ->
+ receive
+ a ->
+ io:format("going to state a~n", []),
+ a(S);
+ idle ->
+ io:format("going to state idle~n", []),
+ idle(S)
+ after 10000 ->
+ io:format("timeout in b~n", []),
+ idle(S)
+ end.
+
+code_change(_OldVsn, _State, _Extra) ->
+ {ok, {newstate, data_vsn()}}.
+
+%% Calls to hibernate can be anywhere in the code.
+eventually_b(S) ->
+ plain_fsm:hibernate(?MODULE,b,[S]).
+
28 src/plain_fsm.app.src
@@ -0,0 +1,28 @@
+%% -*- erlang -*-
+%%==============================================================================
+%% Copyright 2010 Erlang Solutions Ltd.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%==============================================================================
+
+%% @author Ulf Wiger <ulf.wiger@erlang-solutions.com>
+%% @doc This is a container for plain_fsm modules.
+%% @end
+{application, plain_fsm,
+ [{description, "Plain_fsm library"},
+ {vsn, "1.1"},
+ {registered, []},
+ {applications, [kernel, stdlib, syntax_tools]},
+ {env, []}
+ ]}.
+
636 src/plain_fsm.erl
@@ -0,0 +1,636 @@
+%%==============================================================================
+%% Copyright 2010 Erlang Solutions Ltd.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%==============================================================================
+
+%%-------------------------------------------------------------------
+%% File : plain_fsm.erl
+%% @author Ulf Wiger, <ulf.wiger@ericsson.com>
+%% @end
+%% Created : 29 Jan 2004 by Ulf Wiger <ulf.wiger@ericsson.com>
+%%-------------------------------------------------------------------
+
+%% @doc A behaviour/support library for writing plain Erlang FSMs.
+%%
+%% <p>This module implements an OTP behaviour for writing plain Erlang FSMs,
+%% alleviating a long-standing gripe of mine that the OTP behaviours, for all
+%% their power, force programmers into a coding style that is very much
+%% different from that taught in the Basic Erlang Course (or the book, or
+%% online tutorials, ...) -- the type of programming that made us want to
+%% use Erlang in the first place.</p>
+%%
+%% <p>Only in my old age have I begun to understand fully what a sacrifice
+%% this is. See <a href="pots/index.html">
+%% Programming Models for Concurrency </a> for a detailed discussion of
+%% the issues involved.</p>
+%%
+%% <p>The requirements that drove us away from plain Erlang programming
+%% in the first place were:
+%% <ul>
+%% <li><b>The need to support <i>system messages</i></b> to control upgrade,
+%% state inspection, shutdown, etc. The plain_fsm library solves this in a
+%% reasonable way, I think.</li>
+%% <li><b>The need for debugging support</b>. The debugging support in
+%% e.g. gen_server is, I believe, rendered obsolete by the new powerful
+%% trace support (and dbg) in later versions of OTP.</li>
+%% <li>In the case of gen_server, <b>reducing the need to reinvent the
+%% wheel</b>, a valid point, but more so for e.g. the client side of
+%% gen_server:call(). In a protocol state machine, the only thing that
+%% really needs reusing is the handling of system messages.</li>
+%% </ul>
+%% </p>
+%%
+%% <p>However, the behaviours provided by OTP for FSM programming,
+%% <code>gen_server</code> and <code>gen_fsm</code> (<code>gen_server</code>
+%% is perhaps a more common choice than <code>gen_fsm</code>), both have the
+%% distinct drawback that you cannot normally start with a classic
+%% Erlang design and then migrate to a behaviour without significant
+%% code rewrite. In addition, the two behaviours are semantically different
+%% from the classic Erlang design</p>
+%%
+%% <h2>Using plain_fsm</h2>
+%%
+%% <p>First, write your state machine without worrying about OTP system
+%% messages. Once you're happy with it, figure out where you really want
+%% to handle system messages. Normally, it will suffice to do it in a fairly
+%% stable state. A good rule of thumb is that the top-level state machine
+%% should handle system messages, while the transient (sub-) states
+%% shouldn't</p>
+%%
+%% <p>In the states where you want to handle system messages, you have
+%% three choices:</p>
+%%
+%% <h3>(A) Insert the system messages in the receive clause:</h3>
+%%
+%% <pre>
+%% idle(S) ->
+%% Parent = plain_fsm:info(parent),
+%% receive
+%% {system, From, Req} ->
+%% plain_fsm:handle_system_msg(
+%% From, Req, S, fun(S1) -> idle(S1) end);
+%% {'EXIT', Parent, Reason} ->
+%% plain_fsm:parent_EXIT(Reason, S);
+%% ... %% your original code here
+%% end.
+%% </pre>
+%%
+%% <p>This has the advantage that everyone can understand what's going on.
+%% The part that plain_fsm.erl helps you with is the set of functions
+%% <code>system_code_change()</code>, <code>system_continue()</code>,
+%% <code>system_shutdown()</code>, <code>format_status()</code>, which
+%% are required callbacks when you handle system messages directly.</p>
+%%
+%% <h3>(B) Handle system messages and unknown messages together:</h3>
+%%
+%% <pre>
+%% idle(S) ->
+%% Parent = plain_fsm:info(parent),
+%% receive
+%% ... %% your original code here
+%% Msg ->
+%% plain_fsm:handle_msg(Msg, State, fun(S1) -> idle(S1) end)
+%% end.
+%% </pre>
+%%
+%% <p>This is quite convenient if the receive statement already has a
+%% 'catch-all' clause, discarding unknown messages.
+%% <code>plain_fsm:handle_msg/3</code> will handle system messages properly
+%% and ignore any other message.</p>
+%%
+%% <h3>(C) Write a pseudo wrapper function around your receive clause:</h3>
+%%
+%% <pre>
+%% idle(S) ->
+%% plain_fsm:extended_receive(
+%% receive
+%% ... %% your original code
+%% end).
+%% </pre>
+%%
+%% <p>The function <code>plain_fsm:extended_receive/1</code> is replaced
+%% in a <i>parse_transform</i> into something that looks very much like
+%% the previous program (A). The code, as it reads, requires the reader to
+%% know that the transformation takes place, otherwise the semantics
+%% would be confusing (you cannot solve the problem using a real function
+%% that way.) On the plus side, this is a fairly small violation of both
+%% the original code and Erlang's semantics.</p>
+%%
+%% <p><i>Note that for this to work, you must include "plain_fsm.hrl"
+%% in your module.</i></p>
+%%
+%% <h4>Example</h4>
+%% <p>In the module <a href="../src/fsm_example.erl">fsm_example.erl</a>
+%% (included in the plain_fsm package), we choose to handle system
+%% messages in the idle state. The example code is runnable, and supports
+%% suspend, resume, status inspection, and code change.</p>
+%% <p>Imagine that the code initially looked like this:</p>
+%% <pre>
+%% idle(S) ->
+%% receive
+%% a ->
+%% io:format("going to state a~n", []),
+%% a(S);
+%% b ->
+%% io:format("going to state b~n", []),
+%% b(S)
+%% after 10000 ->
+%% io:format("timeout in idle~n", []),
+%% idle(S)
+%% end).
+%% </pre>
+%%
+%% <p>The change required to handle system messages is as follows:</p>
+%% <pre>
+%% idle(S) ->
+%% {@link extended_receive/1. plain_fsm:extended_receive}(
+%% receive
+%% a ->
+%% io:format("going to state a~n", []),
+%% a(S);
+%% b ->
+%% io:format("going to state b~n", []),
+%% b(S)
+%% after 10000 ->
+%% io:format("timeout in idle~n", []),
+%% idle(S)
+%% end).
+%% </pre>
+%%
+%% <p>In addition, we change the start function from, in this case:</p>
+%% <pre>
+%% spawn_link() ->
+%% spawn_link(fun() ->
+%% process_flag(trap_exit, true),
+%% idle(mystate)
+%% end).
+%% </pre>
+%% <p>Is changed into:</p>
+%% <pre>
+%% spawn_link() ->
+%% {@link spawn_link/2. plain_fsm:spawn_link}(?MODULE, fun() ->
+%% process_flag(trap_exit,true),
+%% idle(mystate)
+%% end).
+%% </pre>
+%% <p>See also {@link spawn/2. spawn/2} and {@link spawn_opt/3. spawn_opt/3}
+%% for information on other possible start functions.</p>
+%% <p>To be fully compliant, you also need to supply a code_change/3 function.
+%% See {@link behaviour_info/1. behaviour_info/1} for details.</p>
+%% @end
+
+
+
+-module(plain_fsm).
+
+%% Functions to be used for starting the state machine:
+-export([spawn/2,
+ spawn_link/2,
+ spawn_opt/3, % (Mod, StartF, Opts)
+ spawn_opt/4, % (Node, Mod, StartF, Opts)
+ start_opt/4]). % (Mod, InitF, Timeout, Opts)
+
+%% Functions to be called from within the state machine:
+-export([extended_receive/1,
+ hibernate/3,
+ handle_system_msg/4,
+ handle_msg/3,
+ parent_EXIT/2,
+ store_name/1,
+ info/1]).
+
+
+%% Linter callback
+-export([behaviour_info/1]).
+
+%% Callbacks used by the sys.erl module
+-export([system_continue/3,
+ system_terminate/4,
+ system_code_change/4,
+ format_status/2]).
+
+%% Wrapper function used for waking up from hibernation
+-export([wake_up/5]).
+
+%% Internal housekeeping records. The split into two records is used
+%% to separate the variables that are passed as explicit arguments in
+%% the sys API from the ones that are embedded in the 'state'.
+%% (the #sys{} record is the one being embedded...)
+%%
+-record(sys, {cont,mod,name}).
+-record(info, {parent,
+ debug = [],
+ sys = #sys{}}).
+
+
+-define(line(Tup), element(2, Tup)).
+
+%% ================ Internal functions ==================
+
+%% @spec behaviour_info(atom()) -> term()
+%% @doc Defines which functions this behaviour expects to be exported from
+%% the user's callback module. plain_fsm requires only code_change/3 to
+%% be present. The semantics of <code>Mod:code_change/3</code> are as follows:
+%% <pre>
+%% code_change(OldVsn, State, Extra) -> {ok, NewState}.
+%% </pre>
+%% <p>The above code is just like it would look like in a gen_server callback
+%% module.</p>
+%% <pre>
+%% code_change(OldVsn, State, Extra) -> {ok, NewState, Options}.
+%% </pre>
+%% <p>where <code>Options</code> may be any of </p>
+%% <ul>
+%% <li><code>{mod, module()}</code>, allowing you to switch callback
+%% modules during a code change.</li>
+%% <li><code>{name, name()}</code>, allowing you to rename the process
+%% (note that you have to handle name registration yourself.)</li>
+%% <li><code>{cont, atom() | function(1)}</code>, allowing you to provide
+%% another continuation (point of entry into your own code after the
+%% code change.)</li>
+%% </ul>
+%% @end
+behaviour_info(callbacks) ->
+ [{code_change, 3}, {data_vsn, 0}];
+behaviour_info(_Other) ->
+ undefined.
+
+
+%% @spec spawn(Mod::atom(), StartF::function()) -> pid()
+%% @doc Equivalent to <code>proc_lib:spawn(StartF)</code>. This function also
+%% initializes the plain_fsm meta-data.
+%% @end
+spawn(Mod, StartF) ->
+ ?MODULE:spawn_opt(Mod, StartF, []).
+
+%% @spec spawn_link(Mod::atom(), StartF::function()) -> pid()
+%% @doc Equivalent to <code>proc_lib:spawn_link(StartF)</code>.
+%% This function also initializes the plain_fsm meta-data.
+%% @end
+spawn_link(Mod, StartF) ->
+ ?MODULE:spawn_opt(Mod, StartF, [link]).
+
+%% @spec spawn_opt(Mod::atom(), StartF::function(), Opts::list()) -> pid()
+%% @doc Equivalent to <code>proc_lib:spawn_opt(StartF, Opts)</code>.
+%% This function also initializes the plain_fsm meta-data.
+%% @end
+spawn_opt(Mod, StartF, Opts) when is_function(StartF) ->
+ ParentPid = self(),
+ proc_lib:spawn_opt(fun() ->
+ init(Mod, StartF, ParentPid)
+ end, Opts).
+
+
+%% @spec spawn_opt(Node::atom(),Mod::atom(),StartF::function(),Opts::list()) -> pid()
+%% @doc Equivalent to <code>proc_lib:spawn_opt(Node, StartF, Opts)</code>.
+%% This function also initializes the sysFsm meta-data.
+%% @end
+spawn_opt(Node, Mod, StartF, Opts) when is_function(StartF) ->
+ ParentPid = self(),
+ proc_lib:spawn_opt(Node, fun() ->
+ init(Mod, StartF, ParentPid)
+ end, Opts).
+
+start_opt(Mod, InitF, Timeout, Opts) when is_function(InitF, 0) ->
+ Parent = self(),
+ Pid = proc_lib:spawn_opt(fun() ->
+ sync_init(Mod, InitF, Parent)
+ end, Opts),
+ sync_wait(Pid, Timeout).
+
+
+
+%% @spec store_name(Name::term()) -> ok
+%%
+%% @doc stores an internal name for the FSM
+%% (for <code>sys:get_status()</code>).
+%% This can be used if the FSM were started as an anonymous process
+%% (the only kind currently supported).
+%% Note that this function does not register the name. The name stored
+%% is the one that shows up in sys:get_status/1. No restriction is made
+%% here regarding the data type.
+%% @end
+store_name(Name) ->
+ #info{sys = Sys} = I = get({?MODULE, info}),
+ put({?MODULE,info}, I#info{sys = Sys#sys{name = Name}}),
+ ok.
+
+
+%% @spec info(What::atom()) -> term()
+%% What = debug | name | mod | parent
+%% @doc retrieves meta-data for the plain_fsm process.
+%% <p>Description of available meta-data:</p>
+%% <pre>
+%% debug : See the manual for sys.erl
+%% name : Internal name, normally the same as the registered name.
+%% initially undefined, can be set via plain_fsm:store_name/1.
+%% mod : Name of the callback module.
+%% parent: The pid() of the parent process.
+%% </pre>
+%% @end
+info(What) ->
+ case get({?MODULE,info}) of
+ undefined ->
+ exit(badarg);
+ #info{} = I ->
+ case What of
+ debug -> I#info.debug;
+ name -> (I#info.sys)#sys.name;
+ mod -> (I#info.sys)#sys.mod;
+ parent -> I#info.parent
+ end
+ end.
+
+
+%% @spec extended_receive(Expr) -> VOID
+%%
+%% @doc Virtual function used to wrap receive clauses.
+%% <p>This function cannot be called directly, but is intended as a syntactic
+%% wrapper around a receive clause. It will be transformed at compile time
+%% to a set of receive patterns handling system messages and parent
+%% termination according to the OTP rules. The transform requires that
+%% the surrounding function has exactly one argument (the "State" or
+%% "Loop Data".)</p>
+%% <p>To trigger the parse_transform, include the file
+%% <code>plain_fsm.hrl</code> (found in <code>plain_fsm/inc/</code>) in
+%% your module, and the Erlang compiler must be able to find the module
+%% <code>plain_fsm_xform.beam</code>. If <code>erlc</code> is used, this is
+%% accomplished by adding <code>-pa .../plain_fsm/ebin</code> to the
+%% <code>erlc</code> command.</p>
+%% @end
+extended_receive(_Expr) ->
+ exit(cannot_be_called_directly).
+
+%% @spec hibernate(M::atom(), F::atom(), A::[IntState]) -> NEVER_RETURNS
+%%
+%% @doc Virtual function used to wrap a call to the BIF erlang:hibernate/3.
+%% <p>This function cannot be called directly, but translates to the call
+%% <code>erlang:hibernate(plain_fsm,wake_up,[data_vsn(),Module,M,F,A])</code>
+%% where <code>Module:data_vsn()</code> and <code>Module:code_change/3</code>
+%% are expected to exist (the parse_transform will add and export the
+%% function <code>data_vsn() -&lt; 0</code>, if it doesn't already exist.)</p>
+%% <p>The function <code>plain_fsm:wake_up/5</code> will begin by calling
+%% <code>Module:data_vsn()</code>, and if it is the same as before, simply
+%% call <code>apply(M,F,A)</code>. Otherwise, <code>Module:code_change(OldVsn,
+%% IntState, hibernate)</code> will be called first. This allows a plain_fsm
+%% behaviour module to be "bootstrapped" to a new version during hibernation.
+%% </p>
+%% @end
+hibernate(_M, _F, _A) ->
+ exit(cannot_be_called_directly).
+
+wake_up(OldVsn, Module, M, F, [S] = A) ->
+ case Module:data_vsn() of
+ OldVsn ->
+%% io:format("waking up, same code version.~n"
+%% "messages: ~p~n", [process_info(self(), messages)]),
+ apply(M, F, A);
+ _NewVsn ->
+%% io:format("waking up, new code version - "
+%% "will call code_change().~n"
+%% "messages: ~p~n"
+%% "OldState = ~p~n", [process_info(self(), messages), S]),
+ {ok, S1} = Module:code_change(OldVsn, S, hibernate),
+%% io:format("New state = ~p~n", [S1]),
+ apply(M, F, [S1])
+ end.
+
+
+%% @spec parent_EXIT(Reason, State) -> EXIT
+%%
+%% @doc Handles parent termination properly.
+%% <p>This function is called when the parent of a plain_fsm instance dies.
+%% The OTP rules state that the child should die with the same reason
+%% as the parent (especially in the case of Reason='shutdown'.)</p>
+%% @end
+parent_EXIT(Reason, _State) ->
+ %% no callback - don't know if there should be one...
+ exit(Reason).
+
+
+%% @spec handle_system_msg(Req, From, State, Cont::cont()) -> NEVER_RETURNS
+%%
+%% @doc Called when the process receives a system message.
+%% <p>This function never returns. If the program handles system messages
+%% explicitly, this function can be called to handle them in the plain_fsm
+%% way. Example:</p>
+%% <pre>
+%% idle(S) ->
+%% receive
+%% {system, From, Req} ->
+%% plain_fsm:handle_system_msg(From, Req, S, fun(S1) ->
+%% idle(S1)
+%% end);
+%% ...
+%% end.
+%% </pre>
+%% <p>The <code>Cont</code> argument should be either a fun with one argument
+%% (the new state), which jumps back into the user code in the proper place,
+%% or it can be the name of a function (in this case, 'idle'). In the latter
+%% case, the function in question must be exported; in the former case, this
+%% is not necessary.</p>
+%% @end
+handle_system_msg(Req, From, State, Cont) ->
+ #info{parent = Parent, debug = Debug, sys = Sys} = I =
+ get({?MODULE, info}),
+ Sys1 = Sys#sys{cont = Cont},
+ put({?MODULE,info}, I#info{sys = Sys1}),
+ sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug,
+ {Sys1, State}).
+
+
+%% @spec handle_msg(Msg, State, Cont::cont()) -> NEVER_RETURNS
+%%
+%% @doc Called in a "catch-all" clause within a receive statement.
+%% <p>This function never returns. It will handle system messages
+%% properly and ignore anything else.
+%% Example:</p>
+%% <pre>
+%% idle(S) ->
+%% receive
+%% ...
+%% Msg ->
+%% plain_fsm:handle_msg(Msg, S, fun(S1) ->
+%% idle(S1)
+%% end)
+%% end.
+%% </pre>
+%%
+%% <p>Note that this function should <i>only</i> be used if it is known
+%% to be safe to discard unknown messages. In most state machines there should
+%% be at least <i>one</i> state where unknown messages are discarded; in
+%% these states, the handle_msg/3 function can be a convenient way to
+%% handle both unknown messages and system messages.</p>
+%%
+%% <p>The <code>Cont</code> argument should be either a fun with one argument
+%% (the new state), which jumps back into the user code in the proper place,
+%% or it can be the name of a function (in this case, 'idle'). In the latter
+%% case, the function in question must be exported; in the former case, this
+%% is not necessary.</p>
+%% @end
+handle_msg({system, From, Req}, State, Cont) ->
+ handle_system_msg(Req, From, State, Cont);
+handle_msg(_Other, State, Cont) ->
+ %% Unknown message -- ignore.
+ continue(State, Cont).
+
+
+%% @hidden
+%% @spec system_continue(Parent, Debug, IntState) -> USER_CODE
+%%
+%% @doc Internal export; handles the jump back into user code.
+%%
+system_continue(Parent, Debug, IntState) ->
+ #info{} = I = get({?MODULE, info}),
+ {#sys{cont = Cont} = Sys, State} = IntState,
+ put({?MODULE, info}, I#info{parent = Parent, debug = Debug,
+ sys = Sys}),
+ continue(State, Cont).
+
+
+
+continue(State, Cont) when is_function(Cont) ->
+ Cont(State);
+continue(State, Cont) when is_atom(Cont) ->
+ #info{sys = #sys{mod = Mod}} = get({?MODULE, info}),
+ Mod:Cont(State).
+
+
+%% @hidden
+%% @spec system_terminate(Reason, Parent, Debug, IntState) -> EXIT
+%%
+%% @doc Internal export; called if the process is ordered to terminate e g
+%% during upgrade.
+%%
+system_terminate(Reason, _Parent, _Debug, _State) ->
+ exit(Reason).
+
+
+%% @hidden
+%% @spec system_code_change(IntState, Module, OldVsn, Extra) ->
+%% {ok,NewIntState}
+%%
+%% @doc Internal export; called in order to change into a newer version of
+%% the callback module.
+%%
+system_code_change(IntState, Module, OldVsn, Extra) ->
+ {Sys,State} = IntState,
+ case apply(Module, code_change, [OldVsn, State, Extra]) of
+ {ok, NewState} ->
+ {ok, {Sys, NewState}};
+ {ok, NewState, NewOptions} when is_list(NewOptions) ->
+ NewSys = process_options(NewOptions, Sys),
+ {ok, {NewSys, NewState}}
+ end.
+
+
+%% @hidden
+%% @spec format_status(Opt, StatusData) -> term()
+%%
+%% @doc Internal export; called as a result of a call to sys:get_status(FSM).
+%% <p>It is possible to provide a function, <code>format_status/2</code>,
+%% in the callback module. If such a function is exported, it will be called
+%% as <code>Mod:format_status(Opt, [ProcessDictionary, State])</code>, and
+%% should return a <code>[{string(), term()}]</code> tuple list.</p>
+%% <p>This behaviour is borrowed from gen_server.erl. Unfortunately, both
+%% the Mod:format_status/2 callback required by sys.erl, and the optional
+%% Mod:format_status/2 callback supported by gen_server.erl are undocumented.
+%% </p>
+%% @end
+format_status(Opt, StatusData) ->
+ [PDict, SysState, Parent, Debug, IntState] = StatusData,
+ {#sys{mod = Mod, name = Name}, State} = IntState,
+ NameTag = if is_pid(Name) ->
+ pid_to_list(Name);
+ is_atom(Name) ->
+ Name
+ end,
+ Header = lists:concat(["Status for plain_fsm ", NameTag]),
+ Log = sys:get_debug(log, Debug, []),
+ Specific =
+ case erlang:function_exported(Mod, format_status, 2) of
+ true ->
+ case catch Mod:format_status(Opt, [PDict, State]) of
+ {'EXIT', _} -> [{data, [{"State", State}]}];
+ Else -> Else
+ end;
+ _ ->
+ [{data, [{"State", State}]}]
+ end,
+ [{header, Header},
+ {data, [{"Status", SysState},
+ {"Module", Mod},
+ {"Parent", Parent},
+ {"Logged events", Log} |
+ Specific]}].
+
+
+%% ================ Internal functions ==================
+
+init(Mod, StartF, ParentPid) when is_pid(ParentPid) ->
+ I = #info{parent = ParentPid},
+ Sys = I#info.sys,
+ put({?MODULE, info}, I#info{sys = Sys#sys{mod = Mod}}),
+ StartF().
+
+
+sync_init(Mod, InitF, Parent) ->
+ case init(Mod, InitF, Parent) of
+ {reply, Reply, ContF} when is_function(ContF, 0) ->
+ proc_lib:init_ack(Parent, Reply),
+ ContF();
+ {noreply, ContF} when is_function(ContF, 0) ->
+ ContF();
+ Other ->
+ erlang:error({start_error, Other})
+ end.
+
+
+process_options(Opts, Sys) ->
+ lists:foldl(
+ fun({cont, Cont}, S) ->
+ S#sys{cont = Cont};
+ ({mod, Mod}, S) ->
+ S#sys{mod = Mod};
+ ({name, Name}, S) ->
+ S#sys{name = Name}
+ end, Sys, Opts).
+
+
+
+%% Copied from proc_lib.erl
+%%
+sync_wait(Pid, Timeout) ->
+ receive
+ {ack, Pid, Return} ->
+ Return;
+ {'EXIT', Pid, Reason} ->
+ {error, Reason}
+ after Timeout ->
+ unlink(Pid),
+ exit(Pid, kill),
+ flush(Pid),
+ {error, timeout}
+ end.
+
+
+flush(Pid) ->
+ receive
+ {'EXIT', Pid, _} ->
+ true
+ after 0 ->
+ true
+ end.
+
+%% end copy from proc_lib.erl
318 src/plain_fsm_xform.erl
@@ -0,0 +1,318 @@
+%%==============================================================================
+%% Copyright 2010 Erlang Solutions Ltd.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%==============================================================================
+
+%%-------------------------------------------------------------------
+%% File : plain_fsm.erl
+%% @doc Parse transform utility for plain_fsm
+%% @author Ulf Wiger, <ulf.wiger@ericsson.com>
+%% @end
+%% Created : 29 Jan 2004 by Ulf Wiger <ulf.wiger@ericsson.com>
+%%-------------------------------------------------------------------
+-module(plain_fsm_xform).
+
+
+-export([parse_transform/2,
+ format_error/1]).
+
+-record(context, {module,
+ function,
+ arity}).
+
+-define(PLAIN_FSM, plain_fsm).
+
+-define(ERROR(R, T, F, I),
+ begin
+ rpt_error(R, T, F, I),
+
+ throw({error,erl_syntax:get_pos(
+ proplists:get_value(form,I)),{unknown,R}})
+ end).
+
+parse_transform(Forms, _Options) ->
+ [File|_] = [F || {attribute,_,file,{F,_}} <- Forms],
+ try begin
+ {NewTree, _} = xform_plainfsm(Forms),
+ maybe_add_vsn_f(
+ [erl_syntax:revert(T) || T <- lists:flatten(NewTree)])
+ end
+ catch
+ throw:{error,Ln,What} ->
+ {error, [{File, [{Ln,?MODULE,What}]}], []}
+ end.
+
+
+format_error({bad_arity,_Arity}) ->
+ "Enclosing function for extended_receive must be of arity 1.";
+format_error(Other) ->
+ lists:flatten(
+ io_lib:format("unknown error in parse_transform: ~p", [Other])).
+
+
+
+xform_plainfsm(Forms) ->
+ Bef = fun(function, Form, Ctxt) ->
+ {Fun, Arity} = erl_syntax_lib:analyze_function(Form),
+ {Form, Ctxt#context{function = Fun,
+ arity = Arity}, false};
+ (_, Form, Context) ->
+ {Form, Context, false}
+ end,
+ Aft = fun(application, Form, Context, _SubAcc, Acc) ->
+ case erl_syntax_lib:analyze_application(Form) of
+ {?PLAIN_FSM, {extended_receive, 1}} ->
+ case Context#context.arity of
+ 1 ->
+ Fname = Context#context.function,
+ handle_extended_recv(Form, Fname, Acc);
+ Other ->
+ throw({error,erl_syntax:get_pos(Form),
+ {bad_arity,Other}})
+ end;
+ {?PLAIN_FSM, {hibernate, 3}} ->
+ #context{module = Module} = Context,
+ {hibernate(Module, Form), Acc};
+ _ ->
+ {Form, Acc}
+ end;
+ (clause, Form, _Context, true, _Acc) ->
+ {erl_syntax:add_ann(bind_state, Form), true};
+ (function, Form, _Context, true, Acc) ->
+ {Form1, _} =
+ erl_syntax_lib:mapfold_subtrees(
+ fun(Clause, Acc1) ->
+ Anns = erl_syntax:get_ann(Clause),
+ case lists:member(bind_state, Anns) of
+ true ->
+ [Pat] = erl_syntax:clause_patterns(
+ Clause),
+ CBod = erl_syntax:clause_body(Clause),
+ CGd = erl_syntax:clause_guard(Clause),
+ Clause1 =
+ erl_syntax:clause(
+ [erl_syntax:match_expr(
+ erl_syntax:variable(
+ '__FSM_State'),
+ Pat)],
+ CGd,
+ CBod),
+ {Clause1, Acc1};
+ false ->
+ {Clause, Acc1}
+ end
+ end, ok, Form),
+ {Form1, Acc};
+ (_, Form, _Context, _SubAcc, Acc) ->
+ {Form, Acc}
+ end,
+ [Module] = [M || {attribute, _, module, M} <- Forms],
+ transform(Forms, Bef, Aft, #context{module = Module}, []).
+
+
+transform(Forms, Before, After, Context, Acc) ->
+ F1 =
+ fun(Form, Acc0) ->
+ Type = erl_syntax:type(Form),
+ {Form1, Context1, InitSubAcc} =
+ try Before(Type, Form, Context)
+ catch
+ error:Reason ->
+ ?ERROR(Reason, 'before', Before,
+ [{type, Type},
+ {context, Context},
+ {acc, Acc},
+ {form, Form}])
+ end,
+ {Form2, SubAcc2} =
+ case erl_syntax:subtrees(Form1) of
+ [] ->
+ {Form1, InitSubAcc};
+ List ->
+ {NewList, NewSubAcc} =
+ transform(
+ List, Before, After, Context1, InitSubAcc),
+ NewForm = erl_syntax:update_tree(Form, NewList),
+ {NewForm, NewSubAcc}
+ end,
+ Type2 = erl_syntax:type(Form2),
+ try After(Type2, Form2, Context, SubAcc2, Acc0)
+ catch
+ error:Reason2 ->
+ ?ERROR(Reason2, 'after', After,
+ [{type, Type2},
+ {context, Context},
+ {sub_acc, SubAcc2},
+ {acc, Acc0},
+ {form, Form2}])
+ end
+ end,
+ F2 = fun(List, St) when is_list(List) ->
+ mapfoldl(F1, St, List);
+ (Form, St) ->
+ F1(Form, St)
+ end,
+ mapfoldl(F2, Acc, Forms).
+
+%% Slightly modified version of lists:mapfoldl/3
+%% Here, F/2 is able to insert forms before and after the form
+%% in question. The inserted forms are not transformed afterwards.
+mapfoldl(F, Accu0, [Hd|Tail]) ->
+ {Before, Res, After, Accu1} =
+ case F(Hd, Accu0) of
+ {Be, _, Af, _} = Result when is_list(Be), is_list(Af) ->
+ Result;
+ {R1, A1} ->
+ {[], R1, [], A1}
+ end,
+ {Rs, Accu2} = mapfoldl(F, Accu1, Tail),
+ {Before ++ [Res| After ++ Rs], Accu2};
+mapfoldl(F, Accu, []) when is_function(F, 2) -> {[], Accu}.
+
+
+
+rpt_error(Reason, BeforeOrAfter, Fun, Info) ->
+ Fmt = lists:flatten(
+ ["*** ERROR in parse_transform function:~n"
+ "*** Reason = ~p~n"
+ "*** applying ~w fun (~p)~n",
+ ["*** ~10w = ~p~n" || _ <- Info]]),
+ Args = [Reason, BeforeOrAfter, Fun |
+ lists:foldr(
+ fun({K,V}, Acc) ->
+ [K, V | Acc]
+ end, [], Info)],
+ io:format(Fmt, Args).
+
+
+handle_extended_recv(Form, Fname, _Acc) ->
+ [Arg] = erl_syntax:application_arguments(Form),
+ {[get_parent_expr()], extended_recv(Arg, Fname), [], true}.
+
+extended_recv(Arg, Fname) ->
+ case erl_syntax:type(Arg) of
+ receive_expr ->
+ Clauses = erl_syntax:receive_expr_clauses(Arg),
+ Timeout = erl_syntax:receive_expr_timeout(Arg),
+ Action = erl_syntax:receive_expr_action(Arg),
+ Clauses1 = extend_recv(Clauses, erl_syntax:atom(Fname)),
+ erl_syntax:receive_expr(Clauses1, Timeout, Action);
+ _ ->
+ throw(illegal_argument)
+ end.
+
+
+
+
+get_parent_expr() ->
+ {match,0,
+ {var,0,'__FSM_Parent'},
+ {call,0,{remote,0,
+ {atom,0,?PLAIN_FSM},
+ {atom,0,info}},
+ [{atom,0,parent}]}}.
+
+
+extend_recv(Clauses, Cont) ->
+ [erl_syntax:clause(
+ [erl_syntax:tuple([erl_syntax:atom('EXIT'),
+ erl_syntax:variable('__FSM_Parent'),
+ erl_syntax:variable('__FSM_Reason')])],
+ [],
+ [erl_syntax:application(
+ erl_syntax:atom(?PLAIN_FSM),
+ erl_syntax:atom(parent_EXIT),
+ [erl_syntax:variable('__FSM_Reason'),
+ erl_syntax:variable('__FSM_State')])]),
+ erl_syntax:clause(
+ [erl_syntax:tuple([erl_syntax:atom(system),
+ erl_syntax:variable('__FSM_From'),
+ erl_syntax:variable('__FSM_Req')])],
+ [],
+ [erl_syntax:application(
+ erl_syntax:atom(?PLAIN_FSM),
+ erl_syntax:atom(handle_system_msg),
+ [erl_syntax:variable('__FSM_Req'),
+ erl_syntax:variable('__FSM_From'),
+ erl_syntax:variable('__FSM_State'),
+ erl_syntax:fun_expr(
+ [erl_syntax:clause(
+ [erl_syntax:variable('__FSM_Sx')],
+ [],
+ [erl_syntax:application(
+ Cont,
+ [erl_syntax:variable('__FSM_Sx')])])])])
+ ]) | Clauses].
+
+
+hibernate(Module, Form) ->
+ [M, F, A] = erl_syntax:application_arguments(Form),
+ erl_syntax:application(
+ erl_syntax:atom(erlang),
+ erl_syntax:atom(hibernate),
+ [erl_syntax:atom(?PLAIN_FSM),
+ erl_syntax:atom(wake_up),
+ erl_syntax:list(
+ [erl_syntax:application(erl_syntax:atom(data_vsn), []),
+ erl_syntax:atom(Module),
+ M, F, A])]).
+
+
+%% This function hasn't yet been re-written to use syntax_tools.
+%% OTOH, it is pretty stable as it is, as it operates only on the top
+%% level of the form list.
+%%
+maybe_add_vsn_f(Forms) ->
+ {Pre, Fns} = lists:splitwith(
+ fun(F) when is_tuple(F), element(1,F) == function ->
+ false;
+ (_) ->
+ true
+ end, Forms),
+ Line = element(2,hd(lists:reverse(Pre))),
+ %%
+ Pre1 = case is_exported(data_vsn, 0, Forms) of
+ true ->
+ Pre;
+ false ->
+ Pre ++ [{attribute,Line,export,[{data_vsn,0}]}]
+ end,
+ FunExists =
+ lists:any(fun({function,_Line,data_vsn,0,_Clauses}) ->
+ true;
+ (_) ->
+ false
+ end, Fns),
+ Fns1 =
+ case FunExists of
+ true ->
+ Fns;
+ false ->
+ [{eof,LastLine}|RevFns] = lists:reverse(Fns),
+ lists:reverse(
+ [{eof,LastLine+2},
+ {function,LastLine,data_vsn,0,
+ [{clause,LastLine,[],[],[{integer,LastLine+1,0}]}]}
+ | RevFns])
+ end,
+ Pre1 ++ Fns1.
+
+is_exported(Fun, Arity, Forms) ->
+ lists:any(fun({attribute,_,compile,export_all}) ->
+ true;
+ ({attribute,_,export,Exports}) ->
+ lists:member({Fun,Arity}, Exports);
+ (_) ->
+ false
+ end, Forms).

0 comments on commit 68435f8

Please sign in to comment.