%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2000-2011. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
%%
%%----------------------------------------------------------------------
%% Purpose: Parse and evaluate digit maps
%%----------------------------------------------------------------------
%%
%% digitMap = digitString
%% / LWSP "(" LWSP digitStringList LWSP ")" LWSP
%% digitStringList = digitString *( LWSP "|" LWSP digitString )
%% digitString = 1*(digitStringElement)
%% digitStringElement = digitPosition [DOT]
%% digitPosition = digitMapLetter / digitMapRange
%% digitMapRange = ("x" / LWSP "[" LWSP digitLetter LWSP "]" LWSP)
%% digitLetter = *((DIGIT "-" DIGIT ) / digitMapLetter)
%% digitMapLetter = DIGIT ; Basic event symbols
%% / %x41-4B ; a-k
%% / %x61-6B ; A-K
%% / "T" ; Start inter-event timers
%% / "S" ; Short inter-event timers
%% / "L" ; Long inter-event timers, e.g. 16 sec
%% / "Z" ; Long duration modifier
%% DIGIT = %x30-39 ; 0-9
%%
%%----------------------------------------------------------------------
%% Example of a digit map:
%%
%% (0| 00|[1-7]xxx|8xxxxxxx|Fxxxxxxx|Exx|91xxxxxxxxxx|9011x.)
%%
%% DM = "(0| 00|[1-7]xxx|8xxxxxxx|Fxxxxxxx|Exx|91xxxxxxxxxx|9011x.)".
%% DM = "xxx | xxL3 | xxS4".
%% megaco:parse_digit_map(DM).
%% megaco:test_digit_event(DM, "1234").
%% megaco:test_digit_event(DM, "12ssss3").
%% megaco:test_digit_event(DM, "12ssss4").
%% megaco:test_digit_event(DM, "12ssss5").
%%
%%----------------------------------------------------------------------
-module(megaco_digit_map).
-export([parse/1, eval/1, eval/2, report/2, test/2]). % Public
-export([test_eval/2]). % Internal
-include_lib("megaco/src/app/megaco_internal.hrl").
-include("megaco_message_internal.hrl").
-include_lib("megaco/src/text/megaco_text_tokens.hrl").
-record(state_transition, {mode, next, cont}).
-record(timers, {mode = state_dependent,
start = 0,
short = timer_to_millis(3),
long = timer_to_millis(9),
duration = 100, % (not used) 100 ms <-> 9.9 sec
unexpected = reject}). % ignore | reject
%%----------------------------------------------------------------------
%% Parses a digit map body, represented as a list of chars,
%% into a list of state transitions.
%%
%% Returns {ok, StateTransitionList} | {error, Reason}
%%
%%----------------------------------------------------------------------
parse(DigitMapBody) when is_list(DigitMapBody) ->
?d("parse -> entry with"
"~n DigitMapBody: ~p", [DigitMapBody]),
case parse_digit_map(DigitMapBody) of
{ok, STL} ->
{ok, duration_cleanup(STL, [])};
{error, Reason} ->
{error, Reason}
end;
parse(_DigitMapBody) ->
{error, not_a_digit_map_body}.
duration_cleanup([], Acc) ->
Acc;
duration_cleanup([STL|T], Acc) ->
#state_transition{cont = Events} = STL,
Events2 = duration_events_cleanup(Events, []),
duration_cleanup(T, [STL#state_transition{cont = Events2}|Acc]).
duration_events_cleanup([], Acc) ->
lists:reverse(Acc);
duration_events_cleanup([duration_event, Event|Events], Acc) ->
duration_events_cleanup(Events, [{duration_event, Event}|Acc]);
duration_events_cleanup([Event|Events], Acc) ->
duration_events_cleanup(Events, [Event|Acc]).
parse_digit_map(Chars) ->
parse_digit_map(Chars, 1, [], []).
parse_digit_map(Chars, Line, DS, STL) ->
?d("parse_digit_map -> entry with"
"~n Chars: ~p"
"~n DS: ~p", [Chars, DS]),
case megaco_text_scanner:skip_sep_chars(Chars, Line) of
{[], _Line2} when (DS =/= []) ->
case parse_digit_string(DS) of
{ok, DS2} ->
ST = #state_transition{mode = state_dependent,
next = start,
cont = DS2},
STL2 = lists:reverse([ST | STL]),
{ok, STL2};
{error, Reason} ->
{error, Reason}
end;
{[Char | Chars2], Line2} ->
case Char of
$( when (DS =:= []) andalso (STL =:= []) ->
parse_digit_map(Chars2, Line2, DS, STL);
$) when (DS =/= []) ->
case megaco_text_scanner:skip_sep_chars(Chars2, Line2) of
{[], _Line3} ->
case parse_digit_string(DS) of
{ok, DS2} ->
ST = #state_transition{mode = state_dependent,
next = start,
cont = DS2},
STL2 = lists:reverse([ST | STL]),
{ok, STL2};
{error, Reason} ->
{error, Reason}
end;
{Chars3, Line3} ->
Trash = lists:reverse(Chars3),
{error, {round_bracket_mismatch, Trash, Line3}}
end;
$| when (DS =/= []) ->
case parse_digit_string(DS) of
{ok, DS2} ->
ST = #state_transition{mode = state_dependent,
next = start,
cont = DS2},
parse_digit_map(Chars2, Line2, [], [ST | STL]);
{error, Reason} ->
{error, Reason}
end;
_ when ( Char =/= $( ) andalso
( Char =/= $| ) andalso
( Char =/= $) ) ->
parse_digit_map(Chars2, Line2, [Char | DS], STL);
_ ->
{error, {round_bracket_mismatch, Line2}}
end;
{[], Line2} ->
{error, {digit_string_expected, Line2}}
end.
parse_digit_string(Chars) ->
?d("parse_digit_string -> entry with"
"~n Chars: ~p", [Chars]),
parse_digit_string(Chars, []).
parse_digit_string([Char | Chars], DS) ->
?d("parse_digit_string -> entry with"
"~n Char: ~p"
"~n Chars: ~p"
"~n DS: ~p", [[Char], Chars, DS]),
case Char of
$] ->
parse_digit_letter(Chars, [], DS);
$[ ->
{error, square_bracket_mismatch};
$x ->
parse_digit_string(Chars, [{range, $0, $9} | DS]);
$. ->
parse_digit_string(Chars, [zero_or_more | DS]);
I when (I >= $0) andalso (I =< $9) ->
parse_digit_string(Chars, [{single, I} | DS]);
A when (A >= $a) andalso (A =< $k) ->
parse_digit_string(Chars, [{single, A} | DS]);
A when (A >= $A) andalso (A =< $K) ->
parse_digit_string(Chars, [{single, A} | DS]);
$S ->
parse_digit_string(Chars, [use_short_timer | DS]);
$s ->
parse_digit_string(Chars, [use_short_timer | DS]);
$L ->
parse_digit_string(Chars, [use_long_timer | DS]);
$l ->
parse_digit_string(Chars, [use_long_timer | DS]);
$Z when length(Chars) > 0 ->
parse_digit_string(Chars, [duration_event | DS]);
$z when length(Chars) > 0 ->
parse_digit_string(Chars, [duration_event | DS]);
$Z ->
{error, duration_not_allowed_as_last_char};
$z ->
{error, duration_not_allowed_as_last_char};
BadChar ->
{error, {illegal_char_in_digit_string, BadChar}}
end;
parse_digit_string([], DM) ->
?d("parse_digit_string -> entry when done with"
"~n DM: ~p", [DM]),
{ok, DM}.
parse_digit_letter([Char | Chars], DL, DS) ->
?d("parse_digit_letter -> entry with"
"~n Char: ~p"
"~n Chars: ~p"
"~n DL: ~p"
"~n DS: ~p", [[Char], Chars, DL, DS]),
case Char of
$[ ->
parse_digit_string(Chars, [{letter, DL} | DS]);
$] ->
{error, square_bracket_mismatch};
To when (To >= $0) andalso (To =< $9) ->
case Chars of
[$-, From | Chars2] when (From >= $0) andalso (From =< $9) ->
parse_digit_letter(Chars2, [{range, From, To} | DL], DS);
_ ->
parse_digit_letter(Chars, [{single, To} | DL], DS)
end;
A when (A >= $a) andalso (A =< $k) ->
parse_digit_letter(Chars, [{single, A} | DL], DS);
A when (A >= $A) andalso (A =< $K) ->
parse_digit_letter(Chars, [{single, A} | DL], DS);
$S ->
parse_digit_letter(Chars, [use_short_timer | DL], DS);
$s ->
parse_digit_letter(Chars, [use_short_timer | DL], DS);
$L ->
parse_digit_letter(Chars, [use_long_timer | DL], DS);
$l ->
parse_digit_letter(Chars, [use_long_timer | DL], DS);
$Z ->
parse_digit_letter(Chars, [duration_event | DL], DS);
$z ->
parse_digit_letter(Chars, [duration_event | DL], DS);
BadChar ->
{error, {illegal_char_between_square_brackets, BadChar}}
end;
parse_digit_letter([], _DL, _DS) ->
{error, square_bracket_mismatch}.
%%----------------------------------------------------------------------
%% Collect digit map letters according to digit map
%% Returns {ok, Letters} | {error, Reason}
%%----------------------------------------------------------------------
eval(DMV) when is_record(DMV, 'DigitMapValue') ->
case parse(DMV#'DigitMapValue'.digitMapBody) of
{ok, DigitMapBody} ->
eval(DigitMapBody, DMV);
{error, Reason} ->
{error, Reason}
end;
eval(STL) when is_list(STL) ->
eval(STL, #timers{}).
eval(STL, #'DigitMapValue'{startTimer = Start,
shortTimer = Short,
longTimer = Long,
durationTimer = Duration}) ->
Timers = #timers{start = timer_to_millis(Start),
short = timer_to_millis(Short),
long = timer_to_millis(Long),
duration = duration_to_millis(Duration)},
eval(STL, Timers);
eval(STL, {ignore, #'DigitMapValue'{startTimer = Start,
shortTimer = Short,
longTimer = Long,
durationTimer = Duration}}) ->
Timers = #timers{start = timer_to_millis(Start),
short = timer_to_millis(Short),
long = timer_to_millis(Long),
duration = duration_to_millis(Duration),
unexpected = ignore},
eval(STL, Timers);
eval(STL, {reject, #'DigitMapValue'{startTimer = Start,
shortTimer = Short,
longTimer = Long,
durationTimer = Duration}}) ->
Timers = #timers{start = timer_to_millis(Start),
short = timer_to_millis(Short),
long = timer_to_millis(Long),
duration = duration_to_millis(Duration),
unexpected = reject},
eval(STL, Timers);
eval(STL, Timers) when is_list(STL) andalso
is_record(hd(STL), state_transition) andalso
is_record(Timers, timers) ->
?d("eval -> entry with"
"~n STL: ~p"
"~n Timers: ~p", [STL, Timers]),
case collect(start, mandatory_event, Timers, lists:reverse(STL), []) of
{error, _} = Error ->
?d("eval -> error:"
"~n Error: ~p", [Error]),
Error;
OK ->
?d("eval -> ok:"
"~n OK: ~p", [OK]),
OK
end;
eval(DigitMapBody, ignore) ->
eval(DigitMapBody, #timers{unexpected = ignore});
eval(DigitMapBody, reject) ->
eval(DigitMapBody, #timers{unexpected = reject});
eval(DigitMapBody, Timers) ->
case parse(DigitMapBody) of
{ok, STL} ->
eval(STL, Timers);
{error, Reason} ->
{error, Reason}
end.
%% full | unambiguous
collect(Event, State, Timers, STL, Letters) ->
?d("collect -> entry with"
"~n Event: ~p"
"~n State: ~p"
"~n Timers: ~p"
"~n STL: ~p", [Event, State, Timers, STL]),
case handle_event(Event, State, Timers, STL, Letters) of
{completed_full, _Timers2, _STL2, Letters2} ->
completed(full, Letters2);
{completed, _Timers2, _STL2, Letters2} ->
completed(unambiguous, Letters2);
{State2, Timers2, STL2, Letters2} ->
?d("collect -> "
"~n State2: ~p"
"~n Timers2: ~p"
"~n Letters2: ~p", [State2, Timers2, Letters2]),
MaxWait = choose_timer(State2, Event, Timers2),
?d("collect -> Timer chosen: "
"~n MaxWait: ~p", [MaxWait]),
receive
{?MODULE, _FromPid, Event2} ->
?d("collect -> Got event: "
"~n ~p", [Event2]),
collect(Event2, State2, Timers2, STL2, Letters2)
after MaxWait ->
?d("collect -> timeout after ~w", [MaxWait]),
collect(inter_event_timeout,
State2, Timers2, STL2, Letters2)
end;
{error, Reason} ->
?d("collect -> error: "
"~n Reason: ~p", [Reason]),
{error, Reason}
end.
choose_timer(_State, start, #timers{start = 0}) ->
?d("choose_timer(start) -> entry", []),
infinity;
choose_timer(_State, start, #timers{start = T}) ->
?d("choose_timer(start) -> entry with"
"~n T: ~p", [T]),
T;
choose_timer(State, _Event, T) ->
?d("choose_timer(~p) -> entry with"
"~n State: ~p"
"~n T: ~p", [_Event, State, T]),
do_choose_timer(State, T).
do_choose_timer(mandatory_event, #timers{mode = state_dependent, long = T}) ->
T;
do_choose_timer(optional_event, #timers{mode = state_dependent, short = T}) ->
T;
do_choose_timer(_State, #timers{mode = use_short_timer, short = T}) ->
T;
do_choose_timer(_State, #timers{mode = use_long_timer, long = T}) ->
T.
timer_to_millis(asn1_NOVALUE) -> infinity;
timer_to_millis(infinity) -> infinity;
timer_to_millis(Seconds) -> timer:seconds(Seconds).
%% Time for duration is in hundreds of milliseconds
duration_to_millis(asn1_NOVALUE) -> 100;
duration_to_millis(Time) when is_integer(Time) -> Time*100.
completed(Kind, {Letters, Event}) when is_list(Letters) ->
?d("completed -> entry with"
"~n Kind: ~p"
"~n Event: ~s", [Kind, [Event]]),
{ok, {Kind, duration_letter_cleanup(Letters, []), Event}};
completed(Kind, Letters) when is_list(Letters) ->
?d("completed -> entry with"
"~n Kind: ~p", [Kind]),
{ok, {Kind, duration_letter_cleanup(Letters, [])}}.
duration_letter_cleanup([], Acc) ->
Acc;
duration_letter_cleanup([{long, Letter}|Letters], Acc) ->
duration_letter_cleanup(Letters, [$Z,Letter|Acc]);
duration_letter_cleanup([Letter|Letters], Acc) ->
duration_letter_cleanup(Letters, [Letter|Acc]).
unexpected_event(Event, STL, Letters) ->
Expected = [Next || #state_transition{next = Next} <- STL],
SoFar = lists:reverse(Letters),
Reason = {unexpected_event, Event, SoFar, Expected},
{error, Reason}.
%%----------------------------------------------------------------------
%% Handles a received event according to digit map
%% State ::= optional_event | mandatory_event
%%
%% Returns {State, NewSTL, Letters} | {error, Reason}
%%----------------------------------------------------------------------
handle_event(inter_event_timeout, optional_event, Timers, STL, Letters) ->
{completed_full, Timers, STL, Letters}; % 7.1.14.5 2
handle_event(cancel, _State, _Timers, STL, Letters) ->
unexpected_event(cancel, STL, Letters);
handle_event(start, _State, Timers, STL, Letters) ->
{State2, Timers2, STL2} = compute(Timers, STL),
{State2, Timers2, STL2, Letters};
handle_event(Event, State, Timers, STL, Letters) ->
?d("handle_event -> entry when"
"~n Event: ~p"
"~n State: ~p"
"~n Timers: ~p"
"~n Letters: ~p", [Event, State, Timers, Letters]),
{STL2, Collect, KeepDur} = match_event(Event, STL),
?d("handle_event -> match event result: "
"~n Collect: ~p"
"~n KeepDur: ~p"
"~n STL2: ~p", [Collect, KeepDur, STL2]),
case STL2 of
[] when (State =:= optional_event) -> % 7.1.14.5 5
?d("handle_event -> complete-full with event - 7.1.14.5 5", []),
{completed_full, Timers, [], {Letters, Event}};
[] when (Timers#timers.unexpected =:= ignore) ->
ok = io:format("<WARNING> Ignoring unexpected event: ~p~n"
"Expected: ~p~n",
[Event, STL]),
{State, Timers, STL, Letters};
[] when (Timers#timers.unexpected =:= reject) ->
?d("handle_event -> unexpected (reject)", []),
unexpected_event(Event, STL, Letters);
_ ->
{State3, Timers2, STL3} = compute(Timers, STL2),
?d("handle_event -> computed: "
"~n State3: ~p"
"~n Timers2: ~p"
"~n STL3: ~p", [State3, Timers2, STL3]),
case Collect of
true when (KeepDur =:= true) ->
{State3, Timers2, STL3, [Event | Letters]};
true ->
case Event of
{long, ActualEvent} ->
{State3, Timers2, STL3, [ActualEvent | Letters]};
_ ->
{State3, Timers2, STL3, [Event | Letters]}
end;
false ->
{State3, Timers2, STL3, Letters}
end
end.
match_event(Event, STL) ->
MatchingDuration = matching_duration_event(Event, STL),
match_event(Event, STL, [], false, false, MatchingDuration).
match_event(Event, [ST | OldSTL], NewSTL, Collect, KeepDur, MatchingDuration)
when is_record(ST, state_transition) ->
?d("match_event -> entry with"
"~n Event: ~p"
"~n ST: ~p"
"~n NewSTL: ~p"
"~n Collect: ~p"
"~n KeepDur: ~p"
"~n MatchingDuration: ~p",
[Event, ST, NewSTL, Collect, KeepDur, MatchingDuration]),
case ST#state_transition.next of
{single, Event} ->
?d("match_event -> keep ST (1)", []),
match_event(Event, OldSTL, [ST | NewSTL], true, KeepDur,
MatchingDuration);
{single, Single} when (Event =:= {long, Single}) andalso
(MatchingDuration =:= false) ->
%% Chap 7.1.14.5 point 4
?d("match_event -> keep ST - change to ordinary event (2)", []),
match_event(Event, OldSTL, [ST | NewSTL], true, KeepDur,
MatchingDuration);
{range, From, To} when (Event >= From) andalso (Event =< To) ->
?d("match_event -> keep ST (3)", []),
ST2 = ST#state_transition{next = {single, Event}},
match_event(Event, OldSTL, [ST2 | NewSTL], true, KeepDur,
MatchingDuration);
{range, From, To} ->
case Event of
{long, R} when (R >= From) andalso
(R =< To) andalso
(MatchingDuration =:= false) ->
?d("match_event -> keep ST (4)", []),
ST2 = ST#state_transition{next = {single, R}},
match_event(Event, OldSTL, [ST2 | NewSTL], true, true,
MatchingDuration);
_ ->
?d("match_event -> drop ST - "
"change to ordinary event (5)", []),
match_event(Event, OldSTL, NewSTL, Collect, KeepDur,
MatchingDuration)
end;
{duration_event, {single, Single}} when (Event =:= {long, Single}) ->
?d("match_event -> keep ST (5)", []),
match_event(Event, OldSTL, [ST | NewSTL], true, true,
MatchingDuration);
{duration_event, {range, From, To}} ->
case Event of
{long, R} when (R >= From) andalso (R =< To) ->
?d("match_event -> keep ST (6)", []),
match_event(Event, OldSTL, [ST | NewSTL], true, true,
MatchingDuration);
_ ->
?d("match_event -> drop ST (7)", []),
match_event(Event, OldSTL, NewSTL, Collect, KeepDur,
MatchingDuration)
end;
Event ->
?d("match_event -> keep ST (8)", []),
match_event(Event, OldSTL, [ST | NewSTL], Collect, KeepDur,
MatchingDuration);
{letter, Letters} ->
case match_letter(Event, Letters, MatchingDuration) of
{true, ChangedEvent} ->
?d("match_event -> keep ST (9)", []),
ST2 = ST#state_transition{next = ChangedEvent},
match_event(Event, OldSTL, [ST2 | NewSTL], true, KeepDur,
MatchingDuration);
true ->
?d("match_event -> keep ST (10)", []),
match_event(Event, OldSTL, [ST | NewSTL], true, KeepDur,
MatchingDuration);
false ->
?d("match_event -> drop ST (11)", []),
match_event(Event, OldSTL, NewSTL, Collect, KeepDur,
MatchingDuration)
end;
_ ->
?d("match_event -> drop ST (12)", []),
match_event(Event, OldSTL, NewSTL, Collect, KeepDur,
MatchingDuration)
end;
match_event(Event, [H | T], NewSTL, Collect, KeepDur0, MatchingDuration)
when is_list(H) ->
?d("match_event -> entry with"
"~n Event: ~p"
"~n H: ~p"
"~n NewSTL: ~p"
"~n Collect: ~p"
"~n KeepDur0: ~p"
"~n MatchingDuration: ~p",
[Event, H, NewSTL, Collect, KeepDur0, MatchingDuration]),
{NewSTL2, _Letters, KeepDur} =
match_event(Event, H, NewSTL, Collect, KeepDur0, MatchingDuration),
?d("match_event -> "
"~n NewSTLs: ~p", [NewSTL2]),
match_event(Event, T, NewSTL2, Collect, KeepDur,
MatchingDuration);
match_event(_Event, [], NewSTL, Collect, KeepDur, _MatchingDuration) ->
?d("match_event -> entry with"
"~n NewSTL: ~p"
"~n Collect: ~p"
"~n KeepDur: ~p", [NewSTL, Collect, KeepDur]),
{lists:reverse(NewSTL), Collect, KeepDur}.
match_letter(_Event, [], _MatchingDuration) ->
false;
match_letter(Event, [Letter | Letters], MatchingDuration) ->
?d("match_letter -> entry with"
"~n Event: ~p"
"~n Letter: ~p",
[Event, Letter]),
case Letter of
{single, Event} ->
?d("match_letter -> keep ST (1)", []),
true;
{single, Single} when (Event =:= {long, Single}) andalso
(MatchingDuration =:= false) ->
%% Chap 7.1.14.5 point 4
?d("match_letter -> keep ST - change to ordinary event (2)", []),
true;
{range, From, To} when (Event >= From) andalso (Event =< To) ->
?d("match_letter -> keep ST (3)", []),
{true, {single, Event}};
{range, From, To} ->
case Event of
{long, R} when (R >= From) andalso
(R =< To) andalso
(MatchingDuration =:= false) ->
?d("match_letter -> keep ST (4)", []),
{true, {single, R}};
_ ->
?d("match_letter -> drop ST - "
"change to ordinary event (5)", []),
match_letter(Event, Letters, MatchingDuration)
end;
{duration_event, {single, Single}} when (Event =:= {long, Single}) ->
?d("match_letter -> keep ST (5)", []),
true;
{duration_event, {range, From, To}} ->
case Event of
{long, R} when (R >= From) andalso (R =< To) ->
?d("match_letter -> keep ST (6)", []),
true;
_ ->
?d("match_letter -> drop ST (7)", []),
match_letter(Event, Letters, MatchingDuration)
end;
_ ->
?d("match_letter -> drop ST (8)", []),
match_letter(Event, Letters, MatchingDuration)
end.
matching_duration_event({long, Event}, STL) ->
Nexts = [Next || #state_transition{next = Next} <- STL],
mde(Event, Nexts);
matching_duration_event(_Event, _STL) ->
false.
mde(_, []) ->
false;
mde(Event, [{duration_event, {single, Event}}|_]) ->
true;
mde(Event, [{duration_event, {range, From, To}}|_])
when Event >= From, Event =< To ->
true;
mde(Event, [_|Nexts]) ->
mde(Event, Nexts).
%%----------------------------------------------------------------------
%% Compute new state transitions
%% Returns {State, Timers, NewSTL}
%%----------------------------------------------------------------------
compute(Timers, OldSTL) ->
?d("compute -> entry with"
"~n Timers: ~p"
"~n OldSTL: ~p", [Timers, OldSTL]),
{State, GlobalMode, NewSTL} =
compute(mandatory_event, state_dependent, OldSTL, []),
?d("compute -> "
"~n State: ~p"
"~n GlobalMode: ~p"
"~n NewSTL: ~p", [State, GlobalMode, NewSTL]),
Timers2 = Timers#timers{mode = GlobalMode},
?d("compute -> "
"~n Timers2: ~p", [Timers2]),
{State, Timers2, NewSTL}.
compute(State, GlobalMode, [ST | OldSTL], NewSTL)
when is_record(ST, state_transition) ->
?d("compute(~w) -> entry with"
"~n GlobalMode: ~p"
"~n ST: ~p"
"~n NewSTL: ~p", [State, GlobalMode, ST, NewSTL]),
Cont = ST#state_transition.cont,
Mode = ST#state_transition.mode,
{State2, GlobalMode2, NewSTL2} =
compute_cont(Cont, Mode, GlobalMode, State, NewSTL),
compute(State2, GlobalMode2, OldSTL, NewSTL2);
compute(State, GlobalMode, [H | T], NewSTL) when is_list(H) ->
?d("compute(~w) -> entry with"
"~n GlobalMode: ~p"
"~n H: ~p"
"~n NewSTL: ~p", [State, GlobalMode, H, NewSTL]),
{State2, GlobalMode2, NewSTL2} = compute(State, GlobalMode, H, NewSTL),
compute(State2, GlobalMode2, T, NewSTL2);
compute(State, GlobalMode, [], NewSTL) ->
?d("compute(~w) -> entry with"
"~n GlobalMode: ~p"
"~n NewSTL: ~p", [State, GlobalMode, NewSTL]),
case NewSTL of
[] -> {completed, GlobalMode, NewSTL};
_ -> {State, GlobalMode, NewSTL}
end.
compute_cont([Next | Cont] = All, Mode, GlobalMode, State, STL) ->
?d("compute_cont -> entry with"
"~n Next: ~p"
"~n Mode: ~p"
"~n GlobalMode: ~p", [Next, Mode, GlobalMode]),
case Next of
%% Retain long timer if that has already been chosen
use_short_timer when GlobalMode =:= use_long_timer ->
compute_cont(Cont, Mode, GlobalMode, State, STL);
use_short_timer ->
Mode2 = use_short_timer,
compute_cont(Cont, Mode2, GlobalMode, State, STL);
use_long_timer ->
Mode2 = use_long_timer,
compute_cont(Cont, Mode2, GlobalMode, State, STL);
[] ->
%% Skip empty list
case Cont of
[zero_or_more | Cont2] ->
compute_cont(Cont2, Mode, GlobalMode, State, STL);
_ ->
compute_cont(Cont, Mode, GlobalMode, State, STL)
end;
_ ->
GlobalMode2 =
case Mode of
state_dependent -> GlobalMode;
_ -> Mode
end,
case Cont of
[zero_or_more | Cont2] ->
ST = make_cont(Mode, Next, All),
compute_cont(Cont2, Mode, GlobalMode2, State, [ST | STL]);
_ ->
ST = make_cont(Mode, Next, Cont),
{State, GlobalMode2, [ST | STL]}
end
end;
compute_cont([], GlobalMode, _Mode, _State, STL) ->
{optional_event, GlobalMode, STL}.
make_cont(Mode, [Next | Cont2], Cont) ->
#state_transition{mode = Mode, next = Next, cont = [Cont2 | Cont]};
make_cont(Mode, Next, Cont) ->
#state_transition{mode = Mode, next = Next, cont = Cont}.
%%----------------------------------------------------------------------
%% Send one or more events to event collector process
%%
%% Events ::= Event* | Event
%% Event ::= $0-$9 | $a-$k | $A-$K | $S | $L | $Z
%% $S means sleep one second
%% $L means sleep ten seconds
%% $Z means cancel
%% Returns ok | {error, Reason}
%%----------------------------------------------------------------------
report(Pid, [H | T])->
case report(Pid, H) of
ok ->
report(Pid, T);
{error, Reason} ->
{error, Reason}
end;
report(_Pid, [])->
ok;
report(Pid, Event) when is_pid(Pid) ->
case Event of
I when I >= $0, I =< $9 -> cast(Pid, Event);
A when A >= $a, A =< $k -> cast(Pid, Event);
A when A >= $A, A =< $K -> cast(Pid, Event);
cancel -> cast(Pid, Event);
$Z -> cast(Pid, cancel);
$z -> cast(Pid, cancel);
$R -> timer:sleep(100); % 100 ms
$r -> timer:sleep(100); % 100 ms
$S -> sleep(1); % 1 sec (1000 ms)
$s -> sleep(1); % 1 sec (1000 ms)
$L -> sleep(10); % 10 sec (10000 ms)
$l -> sleep(10); % 10 sec (10000 ms)
{long, I} when (I >= $0) and (I =< $9) -> cast(Pid, {long, I});
{long, A} when (A >= $a) and (A =< $k) -> cast(Pid, {long, A});
{long, A} when (A >= $A) and (A =< $K) -> cast(Pid, {long, A});
%% {long, I} when (I >= $0) and (I =< $9) -> long(Pid, I);
%% {long, A} when (A >= $a) and (A =< $k) -> long(Pid, A);
%% {long, A} when (A >= $A) and (A =< $K) -> long(Pid, A);
_ -> {error, {illegal_event, Event}}
end.
%% long(Pid, Event) ->
%% cast(Pid, long),
%% cast(Pid, Event).
%%
sleep(Sec) ->
timer:sleep(timer:seconds(Sec)),
ok.
cast(Pid, Event) ->
Pid ! {?MODULE, self(), Event},
ok.
%%----------------------------------------------------------------------
%% Feed digit map collector with events
%% Returns: {ok, Letters} | {error, Reason}
%%----------------------------------------------------------------------
test(DigitMap, Events) ->
Self = self(),
Pid = spawn_link(?MODULE, test_eval, [DigitMap, Self]),
report(Pid, Events),
receive
{Self, Pid, Res} ->
Res;
{'EXIT', Pid, Reason} ->
{error, {'EXIT', Reason}}
end.
test_eval(DigitMap, Parent) ->
Res = eval(DigitMap),
unlink(Parent),
Parent ! {Parent, self(), Res},
exit(normal).