%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2010-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%
%%
-module(diameter_spec_scan).
%%
%% Functions used by the spec file parser in diameter_spec_util.
%%
-export([split/1,
split/2,
parse/1]).
%%% -----------------------------------------------------------
%%% # parse/1
%%%
%%% Output: list of Token
%%%
%%% Token = '{' | '}' | '<' | '>' | '[' | ']'
%%% | '*' | '::=' | ':' | ',' | '-'
%%% | {name, string()}
%%% | {tag, atom()}
%%% | {number, integer() >= 0}
%%%
%%% Tokenize a string. Fails if the string does not parse.
%%% -----------------------------------------------------------
parse(S) ->
parse(S, []).
%% parse/2
parse(S, Acc) ->
acc(split(S), Acc).
acc({T, Rest}, Acc) ->
parse(Rest, [T | Acc]);
acc("", Acc) ->
lists:reverse(Acc).
%%% -----------------------------------------------------------
%%% # split/2
%%%
%%% Output: {list() of Token, Rest}
%%%
%%% Extract a specified number of tokens from a string. Returns a list
%%% of length less than the specified number if there are less than
%%% this number of tokens to be parsed.
%%% -----------------------------------------------------------
split(Str, N)
when N >= 0 ->
split(N, Str, []).
split(0, Str, Acc) ->
{lists:reverse(Acc), Str};
split(N, Str, Acc) ->
case split(Str) of
{T, Rest} ->
split(N-1, Rest, [T|Acc]);
"" = Rest ->
{lists:reverse(Acc), Rest}
end.
%%% -----------------------------------------------------------
%%% # split/1
%%%
%%% Output: {Token, Rest} | ""
%%%
%%% Extract the next token from a string.
%%% -----------------------------------------------------------
split("" = Rest) ->
Rest;
split("::=" ++ T) ->
{'::=', T};
split([H|T])
when H == ${; H == $};
H == $<; H == $>;
H == $[; H == $];
H == $*; H == $:; H == $,; H == $- ->
{list_to_atom([H]), T};
split([H|T]) when $A =< H, H =< $Z;
$0 =< H, H =< $9 ->
{P, Rest} = splitwith(fun is_name_ch/1, [H], T),
Tok = try
{number, read_int(P)}
catch
error:_ ->
{name, P}
end,
{Tok, Rest};
split([H|T]) when $a =< H, H =< $z ->
{P, Rest} = splitwith(fun is_name_ch/1, [H], T),
{{tag, list_to_atom(P)}, Rest};
split([H|T]) when H == $\t;
H == $\s;
H == $\n ->
split(T).
%% read_int/1
read_int([$0,X|S])
when X == $X;
X == $x ->
{ok, [N], []} = io_lib:fread("~16u", S),
N;
read_int(S) ->
list_to_integer(S).
%% splitwith/3
splitwith(Fun, Acc, S) ->
split([] /= S andalso Fun(hd(S)), Fun, Acc, S).
split(true, Fun, Acc, [H|T]) ->
splitwith(Fun, [H|Acc], T);
split(false, _, Acc, S) ->
{lists:reverse(Acc), S}.
is_name_ch(C) ->
is_alphanum(C) orelse C == $- orelse C == $_.
is_alphanum(C) ->
is_lower(C) orelse is_upper(C) orelse is_digit(C).
is_lower(C) ->
$a =< C andalso C =< $z.
is_upper(C) ->
$A =< C andalso C =< $Z.
is_digit(C) ->
$0 =< C andalso C =< $9.