aboutsummaryrefslogtreecommitdiffstats
path: root/lib/megaco/src/engine/megaco_digit_map.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/megaco/src/engine/megaco_digit_map.erl')
-rw-r--r--lib/megaco/src/engine/megaco_digit_map.erl856
1 files changed, 856 insertions, 0 deletions
diff --git a/lib/megaco/src/engine/megaco_digit_map.erl b/lib/megaco/src/engine/megaco_digit_map.erl
new file mode 100644
index 0000000000..de28686d6d
--- /dev/null
+++ b/lib/megaco/src/engine/megaco_digit_map.erl
@@ -0,0 +1,856 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2000-2009. 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 choosen: "
+ "~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 choosen
+ 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).