-module(test_gen_fsm). -behaviour(gen_fsm). %% API functions -export([start_link/1, start_link/2, stop/0, process/1 ]). %% gen_fsm callbacks -export([init/1, lower/2, upper/2, handle_event/3, handle_sync_event/4, handle_info/3, terminate/3, code_change/4]). %% testing -export([test/0]). % define the record to hold data items to be passed from one callback % to the next, for example between calls to lower/2 and upper/2. -record(state, {frompid, % the process ID of the requestor outdata, % the collected output data debug=false % if true, print debugging info }). -define(SERVER, ?MODULE). %%%=================================================================== %%% API functions %%%=================================================================== %%-------------------------------------------------------------------- %% @doc %% Creates a gen_fsm process which calls Module:init/1 to %% initialize. To ensure a synchronized start-up procedure, this %% function does not return until Module:init/1 has returned. %% %% @spec start_link() -> {ok, Pid} | ignore | {error, Error} %% @end %%-------------------------------------------------------------------- start_link(FromPid) -> %gen_fsm:start_link({local, ?SERVER}, ?SERVER, [Data], []). gen_fsm:start_link({local, ?SERVER}, ?SERVER, [FromPid, false], []). start_link(FromPid, Debug) -> case Debug of debug -> gen_fsm:start_link({local, ?SERVER}, ?SERVER, [FromPid, true], []); _ -> gen_fsm:start_link({local, ?SERVER}, ?SERVER, [FromPid, false], []) end. %%-------------------------------------------------------------------- %% @doc %% Process an input sequence and return a result string. %% Sample call: %% test_gen_fsm:start_link(MyPid), %% Data = [ %% {chr "a"}, %% {chr "b"}, %% {chr "c"}, %% {cmd, upper}, %% {chr "d"}, %% {chr "e"}, %% {chr "f"}, %% {cmd, lower}, %% {chr "g"}, %% {chr "h"}, %% {chr "i"}, %% 'end' %% ], %% test_gen_fsm:process(Data), %% receive %% {ok, Result} -> %% ... %% %% @spec process(Data) -> ok %% @end %%-------------------------------------------------------------------- process(Data) -> process_items(Data). process_items([]) -> ok; process_items([Item | Rest]) -> gen_fsm:send_event(?SERVER, Item), process_items(Rest). %%-------------------------------------------------------------------- %% @doc %% Stop the FSM. %% %% @spec stop() -> {ok, String} | {error, Reason} %% @end %%-------------------------------------------------------------------- stop() -> %gen_fsm:stop(?SERVER, normal, infinity), gen_fsm:stop(?SERVER), ok. %%%=================================================================== %%% gen_fsm callbacks %%%=================================================================== %%-------------------------------------------------------------------- %% @private %% @doc %% Whenever a gen_fsm is started using gen_fsm:start/[3,4] or %% gen_fsm:start_link/[3,4], this function is called by the new %% process to initialize. %% %% @spec init(Args) -> {ok, StateName, State} | %% {ok, StateName, State, Timeout} | %% ignore | %% {stop, StopReason} %% @end %%-------------------------------------------------------------------- init([FromPid, Debug]) -> State = #state{frompid=FromPid, outdata=[], debug=Debug}, {ok, lower, State}. %%-------------------------------------------------------------------- %% @private %% @doc %% There should be one instance of this function for each possible %% state name. Whenever a gen_fsm receives an event sent using %% gen_fsm:send_event/2, the instance of this function with the same %% name as the current state name StateName is called to handle %% the event. It is also called if a timeout occurs. %% %% @spec state_name(Event, State) -> %% {next_state, NextStateName, NextState} | %% {next_state, NextStateName, NextState, Timeout} | %% {stop, Reason, NewState} %% @end %%-------------------------------------------------------------------- lower(Event, #state{frompid=FromPid, outdata=OutData, debug=Debug} = State) -> case Debug of true -> io:format("(lower) Event: ~p~n", [Event]); _ -> ok end, Item = Event, {StateName, OutData1} = case Item of 'end' -> OutData2 = lists:reverse(OutData), FromPid ! {ok, OutData2}, {'lower', []}; {cmd, Cmd} -> case Cmd of lower -> {lower, OutData}; upper -> {upper, OutData} end; {chr, Chr} -> OutData2 = [string:to_lower(Chr) | OutData], {lower, OutData2} end, {next_state, StateName, State#state{outdata=OutData1}}. upper(Event, #state{frompid=FromPid, outdata=OutData, debug=Debug} = State) -> case Debug of true -> io:format("(upper) Event: ~p~n", [Event]); _ -> ok end, Item = Event, {StateName, OutData1} = case Item of 'end' -> FromPid ! {ok, OutData}, {'lower', []}; {cmd, Cmd} -> case Cmd of lower -> {lower, OutData}; upper -> {upper, OutData} end; {chr, Chr} -> OutData2 = [string:to_upper(Chr) | OutData], {upper, OutData2} end, {next_state, StateName, State#state{outdata=OutData1}}. %%-------------------------------------------------------------------- %% @private %% @doc %% There should be one instance of this function for each possible %% state name. Whenever a gen_fsm receives an event sent using %% gen_fsm:sync_send_event/[2,3], the instance of this function with %% the same name as the current state name StateName is called to %% handle the event. %% %% @spec state_name(Event, From, State) -> %% {next_state, NextStateName, NextState} | %% {next_state, NextStateName, NextState, Timeout} | %% {reply, Reply, NextStateName, NextState} | %% {reply, Reply, NextStateName, NextState, Timeout} | %% {stop, Reason, NewState} | %% {stop, Reason, Reply, NewState} %% @end %%-------------------------------------------------------------------- %~ state_name(_Event, _From, State) -> %~ Reply = ok, %~ {reply, Reply, state_name, State}. %%-------------------------------------------------------------------- %% @private %% @doc %% Whenever a gen_fsm receives an event sent using %% gen_fsm:send_all_state_event/2, this function is called to handle %% the event. %% %% @spec handle_event(Event, StateName, State) -> %% {next_state, NextStateName, NextState} | %% {next_state, NextStateName, NextState, Timeout} | %% {stop, Reason, NewState} %% @end %%-------------------------------------------------------------------- handle_event(_Event, StateName, State) -> {next_state, StateName, State}. %%-------------------------------------------------------------------- %% @private %% @doc %% Whenever a gen_fsm receives an event sent using %% gen_fsm:sync_send_all_state_event/[2,3], this function is called %% to handle the event. %% %% @spec handle_sync_event(Event, From, StateName, State) -> %% {next_state, NextStateName, NextState} | %% {next_state, NextStateName, NextState, Timeout} | %% {reply, Reply, NextStateName, NextState} | %% {reply, Reply, NextStateName, NextState, Timeout} | %% {stop, Reason, NewState} | %% {stop, Reason, Reply, NewState} %% @end %%-------------------------------------------------------------------- handle_sync_event(_Event, _From, StateName, State) -> Reply = ok, {reply, Reply, StateName, State}. %%-------------------------------------------------------------------- %% @private %% @doc %% This function is called by a gen_fsm when it receives any %% message other than a synchronous or asynchronous event %% (or a system message). %% %% @spec handle_info(Info,StateName,State)-> %% {next_state, NextStateName, NextState} | %% {next_state, NextStateName, NextState, Timeout} | %% {stop, Reason, NewState} %% @end %%-------------------------------------------------------------------- handle_info(_Info, StateName, State) -> {next_state, StateName, State}. %%-------------------------------------------------------------------- %% @private %% @doc %% This function is called by a gen_fsm when it is about to %% terminate. It should be the opposite of Module:init/1 and do any %% necessary cleaning up. When it returns, the gen_fsm terminates with %% Reason. The return value is ignored. %% %% @spec terminate(Reason, StateName, State) -> void() %% @end %%-------------------------------------------------------------------- terminate(Reason, _StateName, _State) -> io:format("Terminated with Reason: ~p~n", [Reason]), ok. %%-------------------------------------------------------------------- %% @private %% @doc %% Convert process state when code is changed %% %% @spec code_change(OldVsn, StateName, State, Extra) -> %% {ok, StateName, NewState} %% @end %%-------------------------------------------------------------------- code_change(_OldVsn, StateName, State, _Extra) -> {ok, StateName, State}. %%%=================================================================== %%% Internal functions %%%=================================================================== test() -> Data = [ {chr, "a"}, {chr, "b"}, {chr, "c"}, {cmd, upper}, {chr, "d"}, {chr, "e"}, {chr, "f"}, {cmd, lower}, {chr, "g"}, {chr, "h"}, {chr, "i"}, 'end' ], % test without debug ?MODULE:start_link(self(), nodebug), ?MODULE:process(Data), receive {ok, Output1} -> io:format("Output: ~p~n", [Output1]), ok end, ?MODULE:stop(), % test with debug io:format("-------------------------------------------~n"), ?MODULE:start_link(self(), debug), ?MODULE:process(Data), receive {ok, Output2} -> io:format("Output: ~p~n", [Output2]), ok end, ?MODULE:stop(), ok.