%% ========================== -*-Erlang-*- =============================
%% EDoc type specification grammar for the Yecc parser generator,
%% adapted from Sven-Olof Nyström's type specification parser.
%%
%% Also contains entry points for parsing things like typedefs,
%% references, and throws-declarations.
%%
%% Copyright (C) 2002-2005 Richard Carlsson
%%
%% 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.
%%
%% Alternatively, you may use this file under the terms of the GNU Lesser
%% General Public License (the "LGPL") as published by the Free Software
%% Foundation; either version 2.1, or (at your option) any later version.
%% If you wish to allow use of your version of this file only under the
%% terms of the LGPL, you should delete the provisions above and replace
%% them with the notice and other provisions required by the LGPL; see
%% <http://www.gnu.org/licenses/>. If you do not delete the provisions
%% above, a recipient may use your version of this file under the terms of
%% either the Apache License or the LGPL.
%%
%% Author contact: [email protected]
%% =====================================================================
Nonterminals
start spec func_type utype_list utype_tuple utypes utype ptypes ptype
nutype function_name where_defs defs defs2 def typedef etype
throws qname ref aref mref lref var_list vars fields field
utype_map utype_map_fields utype_map_field
futype_list bin_base_type bin_unit_type.
Terminals
atom float integer var an_var string start_spec start_typedef start_throws
start_ref
'(' ')' ',' '.' '=>' ':=' '->' '{' '}' '[' ']' '|' '+' ':' '::' '=' '/' '//'
'*' '#' 'where' '<<' '>>' '..' '...'.
Rootsymbol start.
start -> start_spec spec: '$2'.
start -> start_throws throws: '$2'.
start -> start_typedef typedef: '$2'.
start -> start_ref ref: '$2'.
%% Produced in reverse order.
qname -> atom: [tok_val('$1')].
qname -> qname '.' atom: [tok_val('$3') | '$1'].
spec -> func_type where_defs:
#t_spec{type = '$1', defs = '$2'}.
spec -> function_name func_type where_defs:
#t_spec{name = '$1', type = '$2', defs = '$3'}.
where_defs -> 'where' defs: '$2'.
where_defs -> defs: '$1'.
function_name -> atom: #t_name{name = tok_val('$1')}.
func_type -> utype_list '->' utype:
#t_fun{args = element(1, '$1'), range = '$3'}.
%% Paired with line number, for later error reporting
utype_list -> '(' utypes ')' : {lists:reverse('$2'), tok_line('$1')}.
futype_list -> utype_list : '$1'.
futype_list -> '(' '...' ')' : {[#t_var{name = '...'}], tok_line('$1')}.
utype_map -> '#' '{' utype_map_fields '}' : lists:reverse('$3').
utype_map_fields -> '$empty' : [].
utype_map_fields -> utype_map_field : ['$1'].
utype_map_fields -> utype_map_fields ',' utype_map_field : ['$3' | '$1'].
utype_map_field -> utype '=>' utype : #t_map_field{assoc_type = assoc,
k_type = '$1',
v_type = '$3'}.
utype_map_field -> utype ':=' utype : #t_map_field{assoc_type = exact,
k_type = '$1',
v_type = '$3'}.
utype_tuple -> '{' utypes '}' : lists:reverse('$2').
%% Produced in reverse order.
utypes -> '$empty' : [].
utypes -> utype : ['$1'].
utypes -> utypes ',' utype : ['$3' | '$1'].
utype -> nutype string: annotate('$1', tok_val('$2')).
utype -> nutype: '$1'.
nutype -> var '::' ptypes: annotate(union('$3'), tok_val('$1')).
nutype -> ptypes: union('$1').
%% Produced in reverse order.
ptypes -> ptype : ['$1'].
ptypes -> ptypes '+' ptype : ['$3' | '$1'].
ptypes -> ptypes '|' ptype : ['$3' | '$1'].
ptype -> var : #t_var{name = tok_val('$1')}.
ptype -> atom : #t_atom{val = tok_val('$1')}.
ptype -> integer: #t_integer{val = tok_val('$1')}.
ptype -> integer '..' integer: #t_integer_range{from = tok_val('$1'),
to = tok_val('$3')}.
ptype -> float: #t_float{val = tok_val('$1')}.
ptype -> utype_tuple : #t_tuple{types = '$1'}.
ptype -> utype_map : #t_map{types = '$1'}.
ptype -> '[' ']' : #t_nil{}.
ptype -> '[' utype ']' : #t_list{type = '$2'}.
ptype -> '[' utype ',' '...' ']' : #t_nonempty_list{type = '$2'}.
ptype -> utype_list:
if length(element(1, '$1')) == 1 ->
%% there must be exactly one utype in the list
#t_paren{type = hd(element(1, '$1'))};
length(element(1, '$1')) == 0 ->
return_error(element(2, '$1'), "syntax error before: ')'");
true ->
return_error(element(2, '$1'), "syntax error before: ','")
end.
ptype -> futype_list '->' ptype:
#t_fun{args = element(1, '$1'), range = '$3'}.
ptype -> '#' atom '{' '}' :
#t_record{name = #t_atom{val = tok_val('$2')}}.
ptype -> '#' atom '{' fields '}' :
#t_record{name = #t_atom{val = tok_val('$2')},
fields = lists:reverse('$4')}.
ptype -> atom utype_list:
case {tok_val('$1'), element(1, '$2')} of
{nil, []} ->
%% Prefer '[]' before 'nil(). Due to
%% compatibility with Erlang types, which do not
%% separate '[]' from 'nil()'.
#t_nil{};
{list, [T]} ->
%% Prefer '[T]' before 'list(T). Due to
%% compatibility with Erlang types, which do not
%% separate '[T]' from 'list(T)'.
#t_list{type = T};
{'fun', [#t_fun{}=Fun]} ->
%% An incompatible change as compared to EDOc 0.7.6.6.
%% Due to compatibility with Erlang types.
Fun;
{'fun', []} ->
#t_type{name = #t_name{name = function}};
{Name, Args} ->
#t_type{name = #t_name{name = Name},
args = Args}
end.
ptype -> qname ':' atom utype_list :
#t_type{name = #t_name{module = qname('$1'),
name = tok_val('$3')},
args = element(1, '$4')}.
ptype -> '//' atom '/' qname ':' atom utype_list :
#t_type{name = #t_name{app = tok_val('$2'),
module = qname('$4'),
name = tok_val('$6')},
args = element(1, '$7')}.
ptype -> '<<' '>>' : #t_binary{}.
ptype -> '<<' bin_base_type '>>' : #t_binary{base_size = '$2'}.
ptype -> '<<' bin_unit_type '>>' : #t_binary{unit_size = '$2'}.
ptype -> '<<' bin_base_type ',' bin_unit_type '>>' :
#t_binary{base_size = '$2', unit_size = '$4'}.
bin_base_type -> an_var ':' integer: tok_val('$3').
bin_unit_type -> an_var ':' an_var '*' integer : tok_val('$5').
%% Produced in reverse order.
fields -> field : ['$1'].
fields -> fields ',' field : ['$3' | '$1'].
field -> atom '=' utype :
#t_field{name = #t_atom{val = tok_val('$1')}, type = '$3'}.
defs -> '$empty' : [].
defs -> def defs2 : ['$1' | lists:reverse('$2')].
%% Produced in reverse order.
defs2 -> '$empty' : [].
defs2 -> defs2 def : ['$2' | '$1'].
defs2 -> defs2 ',' def : ['$3' | '$1'].
def -> var '=' utype:
#t_def{name = #t_var{name = tok_val('$1')},
type = '$3'}.
def -> atom '(' utypes ')' '=' utype:
build_def(tok_val('$1'), '$2', '$3', '$6').
var_list -> '(' ')' : [].
var_list -> '(' vars ')' : lists:reverse('$2').
%% Produced in reverse order.
vars -> var : [#t_var{name = tok_val('$1')}].
vars -> vars ',' var : [#t_var{name = tok_val('$3')} | '$1'].
typedef -> atom var_list where_defs:
#t_typedef{name = #t_name{name = tok_val('$1')},
args = '$2',
defs = '$3'}.
typedef -> atom var_list '=' utype where_defs:
#t_typedef{name = #t_name{name = tok_val('$1')},
args = '$2',
type = '$4',
defs = '$5'}.
%% References
ref -> aref: '$1'.
ref -> mref: '$1'.
ref -> lref: '$1'.
aref -> '//' atom:
edoc_refs:app(tok_val('$2')).
aref -> '//' atom '/' mref:
edoc_refs:app(tok_val('$2'), '$4').
mref -> qname ':' atom '/' integer:
edoc_refs:function(qname('$1'), tok_val('$3'), tok_val('$5')).
mref -> qname ':' atom '(' ')':
edoc_refs:type(qname('$1'), tok_val('$3')).
mref -> qname:
edoc_refs:module(qname('$1')).
lref -> atom '/' integer:
edoc_refs:function(tok_val('$1'), tok_val('$3')).
lref -> atom '(' ')':
edoc_refs:type(tok_val('$1')).
%% Exception declarations
etype -> utype: '$1'.
throws -> etype where_defs:
#t_throws{type = '$1',
defs = '$2'}.
Header
"%% EDoc function specification parser, generated from the file"
"%% \"edoc_parser.yrl\" by the Yecc parser generator."
"%%"
"%% 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."
"%%"
"%% Alternatively, you may use this file under the terms of the GNU Lesser"
"%% General Public License (the \"LGPL\") as published by the Free Software"
"%% Foundation; either version 2.1, or (at your option) any later version."
"%% If you wish to allow use of your version of this file only under the"
"%% terms of the LGPL, you should delete the provisions above and replace"
"%% them with the notice and other provisions required by the LGPL; see"
"%% <http://www.gnu.org/licenses/>. If you do not delete the provisions"
"%% above, a recipient may use your version of this file under the terms of"
"%% either the Apache License or the LGPL."
"%%"
"%% @private"
"%% @copyright 2002-2005 Richard Carlsson"
"%% @author Richard Carlsson <[email protected]>"
"".
Erlang code.
-export([parse_spec/2, parse_typedef/2, parse_throws/2, parse_ref/2,
parse_see/2, parse_param/2]).
-include("edoc_types.hrl").
%% Multiple entry point hack:
start_spec(Ts, L) -> run_parser(Ts, L, start_spec).
start_typedef(Ts, L) -> run_parser(Ts, L, start_typedef).
start_throws(Ts, L) -> run_parser(Ts, L, start_throws).
start_ref(Ts, L) -> run_parser(Ts, L, start_ref).
%% Error reporting fix
run_parser(Ts, L, Start) ->
case parse([{Start,L} | Ts]) of
{error, {999999,?MODULE,_}} ->
What = case Start of
start_spec -> "specification";
start_typedef -> "type definition";
start_throws -> "exception declaration";
start_ref -> "reference"
end,
{error, {L,?MODULE,["unexpected end of ", What]}};
Other -> Other
end.
%% Utility functions:
tok_val(T) -> element(3, T).
tok_line(T) -> element(2, T).
qname([A]) -> A.
union(Ts) ->
case Ts of
[T] -> T;
_ -> #t_union{types = lists:reverse(Ts)}
end.
annotate(T, A) -> ?add_t_ann(T, A).
build_def(S, P, As, T) ->
case all_vars(As) of
true ->
#t_def{name = #t_type{name = #t_name{name = S},
args = lists:reverse(As)},
type = T};
false ->
return_error(tok_line(P), "variable expected after '('")
end.
all_vars([#t_var{} | As]) ->
all_vars(As);
all_vars(As) ->
As =:= [].
%% ---------------------------------------------------------------------
%% @doc EDoc type specification parsing. Parses the content of
%% <a href="overview-summary.html#ftag-spec">`@spec'</a> declarations.
parse_spec(S, L) ->
case edoc_scanner:string(S, L) of
{ok, Ts, _} ->
case start_spec(Ts, L) of
{ok, Spec} ->
Spec;
{error, E} ->
throw_error({parse_spec, E}, L)
end;
{error, E, _} ->
throw_error({parse_spec, E}, L)
end.
%% ---------------------------------------------------------------------
%% @doc EDoc type definition parsing. Parses the content of
%% <a href="overview-summary.html#gtag-type">`@type'</a> declarations.
parse_typedef(S, L) ->
{S1, S2} = edoc_lib:split_at_stop(S),
N = edoc_lib:count($\n, S1),
L1 = L + N,
Text = edoc_lib:strip_space(S2),
{parse_typedef_1(S1, L), edoc_wiki:parse_xml(Text, L1)}.
parse_typedef_1(S, L) ->
case edoc_scanner:string(S, L) of
{ok, Ts, _} ->
case start_typedef(Ts, L) of
{ok, T} ->
T;
{error, E} ->
throw_error({parse_typedef, E}, L)
end;
{error, E, _} ->
throw_error({parse_typedef, E}, L)
end.
%% ---------------------------------------------------------------------
%% @doc Parses a <a
%% href="overview-summary.html#References">reference</a> to a module,
%% function, type, or application
parse_ref(S, L) ->
case edoc_scanner:string(S, L) of
{ok, Ts, _} ->
case start_ref(Ts, L) of
{ok, T} ->
T;
{error, E} ->
throw_error({parse_ref, E}, L)
end;
{error, E, _} ->
throw_error({parse_ref, E}, L)
end.
%% ---------------------------------------------------------------------
%% @doc Parses the content of
%% <a href="overview-summary.html#ftag-see">`@see'</a> references.
parse_see(S, L) ->
{S1, S2} = edoc_lib:split_at_stop(S),
N = edoc_lib:count($\n, S1),
L1 = L + N,
Text = edoc_lib:strip_space(S2),
{parse_ref(S1, L), edoc_wiki:parse_xml(Text, L1)}.
%% ---------------------------------------------------------------------
%% @doc Parses the content of
%% <a href="overview-summary.html#ftag-param">`@param'</a> tags.
parse_param(S, L) ->
{S1, S2} = edoc_lib:split_at_space(edoc_lib:strip_space(S)),
case edoc_lib:strip_space(S1) of
"" -> throw_error(parse_param, L);
Name ->
Text = edoc_lib:strip_space(S2),
{list_to_atom(Name), edoc_wiki:parse_xml(Text, L)}
end.
%% ---------------------------------------------------------------------
%% @doc EDoc exception specification parsing. Parses the content of
%% <a href="overview-summary.html#ftag-throws">`@throws'</a> declarations.
parse_throws(S, L) ->
case edoc_scanner:string(S, L) of
{ok, Ts, _} ->
case start_throws(Ts, L) of
{ok, Spec} ->
Spec;
{error, E} ->
throw_error({parse_throws, E}, L)
end;
{error, E, _} ->
throw_error({parse_throws, E}, L)
end.
%% ---------------------------------------------------------------------
-spec throw_error(term(), erl_anno:line()) -> no_return().
throw_error({parse_spec, E}, L) ->
throw_error({"specification", E}, L);
throw_error({parse_typedef, E}, L) ->
throw_error({"type definition", E}, L);
throw_error({parse_ref, E}, L) ->
throw_error({"reference", E}, L);
throw_error({parse_throws, E}, L) ->
throw_error({"throws-declaration", E}, L);
throw_error(parse_param, L) ->
throw({error, L, "missing parameter name"});
throw_error({Where, E}, L) when is_list(Where) ->
throw({error,L,{"unknown error parsing ~ts: ~P.",[Where,E,15]}}).
%% vim: ft=erlang