From 290dc2b08a2f92157ac5358fba815f4dbb32f8f2 Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Sat, 18 May 2013 21:02:22 +0200 Subject: Fix reverting of implicit funs Implicit funs parts in plain AST are no longer in concrete form since Erlang/OTP R15. --- lib/syntax_tools/src/erl_syntax.erl | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl index bdb2b5bcd7..409805e95f 100644 --- a/lib/syntax_tools/src/erl_syntax.erl +++ b/lib/syntax_tools/src/erl_syntax.erl @@ -5485,22 +5485,15 @@ revert_implicit_fun(Node) -> arity_qualifier -> F = arity_qualifier_body(Name), A = arity_qualifier_argument(Name), - case {type(F), type(A)} of - {atom, integer} -> - {'fun', Pos, - {function, concrete(F), concrete(A)}}; - _ -> - Node - end; + {'fun', Pos, {function, F, A}}; module_qualifier -> M = module_qualifier_argument(Name), Name1 = module_qualifier_body(Name), - F = arity_qualifier_body(Name1), - A = arity_qualifier_argument(Name1), - case {type(M), type(F), type(A)} of - {atom, atom, integer} -> - {'fun', Pos, - {function, concrete(M), concrete(F), concrete(A)}}; + case type(Name1) of + arity_qualifier -> + F = arity_qualifier_body(Name1), + A = arity_qualifier_argument(Name1), + {'fun', Pos, {function, M, F, A}}; _ -> Node end; -- cgit v1.2.3 From 384d59cedf515d34f1b3dbcad992ffe5261b905d Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Sat, 18 May 2013 21:06:02 +0200 Subject: Fix transformation of implicit funs in igor Function erl_syntax_lib:analyze_implicit_fun/1 should not be called on implicit external funs, as their parts can contain variables instead of atoms and integers. --- lib/syntax_tools/src/igor.erl | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/igor.erl b/lib/syntax_tools/src/igor.erl index d385c2b690..19b1cd592f 100644 --- a/lib/syntax_tools/src/igor.erl +++ b/lib/syntax_tools/src/igor.erl @@ -1803,20 +1803,25 @@ transform_rule(T, Env, St) -> transform_implicit_fun(T, Env, St) -> {T1, St1} = default_transform(T, Env, St), - F = erl_syntax_lib:analyze_implicit_fun(T1), - {V, Text} = case (Env#code.map)(F) of - F -> - %% Not renamed - {none, []}; - {Atom, Arity} -> - %% Renamed - N = rewrite( - erl_syntax:implicit_fun_name(T1), - erl_syntax:arity_qualifier( - erl_syntax:atom(Atom), - erl_syntax:integer(Arity))), - T2 = erl_syntax:implicit_fun(N), - {{value, T2}, ["function was renamed"]} + {V, Text} = case erl_syntax:type(erl_syntax:implicit_fun_name(T1)) of + arity_qualifier -> + F = erl_syntax_lib:analyze_implicit_fun(T1), + case (Env#code.map)(F) of + F -> + %% Not renamed + {none, []}; + {Atom, Arity} -> + %% Renamed + N = rewrite( + erl_syntax:implicit_fun_name(T1), + erl_syntax:arity_qualifier( + erl_syntax:atom(Atom), + erl_syntax:integer(Arity))), + T2 = erl_syntax:implicit_fun(N), + {{value, T2}, ["function was renamed"]} + end; + module_qualifier -> + {none, []} end, {maybe_modified_quiet(V, T1, 2, Text, Env), St1}. -- cgit v1.2.3 From c37d6d74c6bf9064ffbd488b77b424ff25c6bbc0 Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Sun, 19 May 2013 12:26:12 +0200 Subject: Remove a useless function clause in erl_syntax_lib --- lib/syntax_tools/src/erl_syntax_lib.erl | 2 -- 1 file changed, 2 deletions(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_syntax_lib.erl b/lib/syntax_tools/src/erl_syntax_lib.erl index 2c94ac776d..e4665b99fc 100644 --- a/lib/syntax_tools/src/erl_syntax_lib.erl +++ b/lib/syntax_tools/src/erl_syntax_lib.erl @@ -1357,8 +1357,6 @@ analyze_attribute(file, Node) -> analyze_file_attribute(Node); analyze_attribute(record, Node) -> analyze_record_attribute(Node); -analyze_attribute(define, _Node) -> - define; analyze_attribute(spec, _Node) -> spec; analyze_attribute(_, Node) -> -- cgit v1.2.3 From acbca8379bdde12612e27f3313a5c73f4db25381 Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Sat, 10 Nov 2012 17:06:01 +0100 Subject: EEP 37: Funs with names This adds optional names to fun expressions. A named fun expression is parsed as a tuple `{named_fun,Loc,Name,Clauses}` in erl_parse. If a fun expression has a name, it must be present and be the same in every of its clauses. The function name shadows the environment of the expression shadowing the environment and it is shadowed by the environment of the clauses' arguments. An unused function name triggers a warning unless it is prefixed by _, just as every variable. Variable _ is allowed as a function name. It is not an error to put a named function in a record field default value. When transforming to Core Erlang, the named fun Fun is changed into the following expression: letrec 'Fun'/Arity = fun (Args) -> let = 'Fun'/Arity in Case in 'Fun'/Arity where Args is the list of arguments of 'Fun'/Arity and Case the Core Erlang expression corresponding to the clauses of Fun. This transformation allows us to entirely skip any k_var to k_local transformation in the fun's clauses bodies. --- lib/syntax_tools/src/erl_syntax.erl | 126 ++++++++++++++++++++++++++++++++++-- 1 file changed, 122 insertions(+), 4 deletions(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl index 409805e95f..5911502960 100644 --- a/lib/syntax_tools/src/erl_syntax.erl +++ b/lib/syntax_tools/src/erl_syntax.erl @@ -226,6 +226,10 @@ module_qualifier/2, module_qualifier_argument/1, module_qualifier_body/1, + named_fun_expr/2, + named_fun_expr_arity/1, + named_fun_expr_clauses/1, + named_fun_expr_name/1, nil/0, operator/1, operator_literal/1, @@ -442,28 +446,30 @@ %% match_expr %% module_qualifier %% +%% named_fun_expr %% nil %% operator %% parentheses -%% prefix_expr %% +%% prefix_expr %% receive_expr %% record_access -%% %% record_expr +%% %% record_field %% record_index_expr %% rule -%% %% size_qualifier +%% %% string %% text %% try_expr -%% %% tuple +%% %% underscore %% variable %% warning_marker +%% %% %% %% @@ -506,6 +512,7 @@ %% @see macro/2 %% @see match_expr/2 %% @see module_qualifier/2 +%% @see named_fun_expr/1 %% @see nil/0 %% @see operator/1 %% @see parentheses/1 @@ -554,6 +561,7 @@ type(Node) -> {'catch', _, _} -> catch_expr; {'cond', _, _} -> cond_expr; {'fun', _, {clauses, _}} -> fun_expr; + {named_fun, _, _, _} -> named_fun_expr; {'fun', _, {function, _, _}} -> implicit_fun; {'fun', _, {function, _, _, _}} -> implicit_fun; {'if', _, _} -> if_expr; @@ -5615,6 +5623,110 @@ fun_expr_arity(Node) -> length(clause_patterns(hd(fun_expr_clauses(Node)))). +%% ===================================================================== +%% @doc Creates an abstract named fun-expression. If `Clauses' is +%% `[C1, ..., Cn]', the result represents "fun +%% Name C1; ...; Name Cn end". +%% More exactly, if each `Ci' represents +%% "(Pi1, ..., Pim) Gi -> Bi", +%% then the result represents +%% "fun Name(P11, ..., P1m) G1 -> +%% B1; ...; Name(Pn1, ..., Pnm) +%% Gn -> Bn end". +%% +%% @see named_fun_expr_name/1 +%% @see named_fun_expr_clauses/1 +%% @see named_fun_expr_arity/1 + +-record(named_fun_expr, {name :: syntaxTree(), clauses :: [syntaxTree()]}). + +%% type(Node) = named_fun_expr +%% data(Node) = #named_fun_expr{name :: Name, clauses :: Clauses} +%% +%% Name = syntaxTree() +%% Clauses = [syntaxTree()] +%% +%% (See `function' for notes; e.g. why the arity is not stored.) +%% +%% `erl_parse' representation: +%% +%% {named_fun, Pos, Name, Clauses} +%% +%% Clauses = [Clause] \ [] +%% Clause = {clause, ...} +%% +%% See `clause' for documentation on `erl_parse' clauses. + +-spec named_fun_expr(syntaxTree(), [syntaxTree()]) -> syntaxTree(). + +named_fun_expr(Name, Clauses) -> + tree(fun_expr, #named_fun_expr{name = Name, clauses = Clauses}). + +revert_named_fun_expr(Node) -> + Pos = get_pos(Node), + Name = named_fun_expr_name(Node), + Clauses = [revert_clause(C) || C <- named_fun_expr_clauses(Node)], + case type(Name) of + var -> + {named_fun, Pos, concrete(Name), Clauses}; + _ -> + Node + end. + + +%% ===================================================================== +%% @doc Returns the name subtree of a `named_fun_expr' node. +%% +%% @see named_fun_expr/2 + +-spec named_fun_expr_name(syntaxTree()) -> syntaxTree(). + +named_fun_expr_name(Node) -> + case unwrap(Node) of + {named_fun, Pos, Name, _} -> + set_pos(atom(Name), Pos); + Node1 -> + (data(Node1))#named_fun_expr.name + end. + + +%% ===================================================================== +%% @doc Returns the list of clause subtrees of a `named_fun_expr' node. +%% +%% @see named_fun_expr/1 + +-spec named_fun_expr_clauses(syntaxTree()) -> [syntaxTree()]. + +named_fun_expr_clauses(Node) -> + case unwrap(Node) of + {named_fun, _, _, Clauses} -> + Clauses; + Node1 -> + (data(Node1))#named_fun_expr.clauses + end. + + +%% ===================================================================== +%% @doc Returns the arity of a `named_fun_expr' node. The result is +%% the number of parameter patterns in the first clause of the +%% named fun-expression; subsequent clauses are ignored. +%% +%% An exception is thrown if `named_fun_expr_clauses(Node)' +%% returns an empty list, or if the first element of that list is not a +%% syntax tree `C' of type `clause' such that +%% `clause_patterns(C)' is a nonempty list. +%% +%% @see named_fun_expr/1 +%% @see named_fun_expr_clauses/1 +%% @see clause/3 +%% @see clause_patterns/1 + +-spec named_fun_expr_arity(syntaxTree()) -> arity(). + +named_fun_expr_arity(Node) -> + length(clause_patterns(hd(named_fun_expr_clauses(Node)))). + + %% ===================================================================== %% @doc Creates an abstract parenthesised expression. The result %% represents "(Body)", independently of the @@ -5978,6 +6090,8 @@ revert_root(Node) -> revert_match_expr(Node); module_qualifier -> revert_module_qualifier(Node); + named_fun_expr -> + revert_named_fun_expr(Node); nil -> revert_nil(Node); parentheses -> @@ -6219,6 +6333,9 @@ subtrees(T) -> module_qualifier -> [[module_qualifier_argument(T)], [module_qualifier_body(T)]]; + named_fun_expr -> + [[named_fun_expr_name(T)], + named_fun_expr_clauses(T)]; parentheses -> [[parentheses_body(T)]]; prefix_expr -> @@ -6349,6 +6466,7 @@ make_tree(list_comp, [[T], B]) -> list_comp(T, B); make_tree(macro, [[N]]) -> macro(N); make_tree(macro, [[N], A]) -> macro(N, A); make_tree(match_expr, [[P], [E]]) -> match_expr(P, E); +make_tree(named_fun_expr, [[N], C]) -> named_fun_expr(N, C); make_tree(module_qualifier, [[M], [N]]) -> module_qualifier(M, N); make_tree(parentheses, [[E]]) -> parentheses(E); make_tree(prefix_expr, [[F], [A]]) -> prefix_expr(F, A); -- cgit v1.2.3 From 5360e3097fad1f05965d309b672720a864c02e55 Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Thu, 12 Dec 2013 23:41:28 +0100 Subject: Fix reverting of local implicit funs in erl_syntax This partially reverts 290dc2b08a2f92157ac5358fba815f4dbb32f8f2 in which implicit funs were mistakenly thought to be using abstract terms for their name and arity. --- lib/syntax_tools/src/erl_syntax.erl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl index 409805e95f..775b2cc109 100644 --- a/lib/syntax_tools/src/erl_syntax.erl +++ b/lib/syntax_tools/src/erl_syntax.erl @@ -5485,7 +5485,13 @@ revert_implicit_fun(Node) -> arity_qualifier -> F = arity_qualifier_body(Name), A = arity_qualifier_argument(Name), - {'fun', Pos, {function, F, A}}; + case {type(F), type(A)} of + {atom, integer} -> + {'fun', Pos, + {function, concrete(F), concrete(A)}}; + _ -> + Node + end; module_qualifier -> M = module_qualifier_argument(Name), Name1 = module_qualifier_body(Name), -- cgit v1.2.3 From eb7c881a62e306f598a930b4781ecbbb67e37424 Mon Sep 17 00:00:00 2001 From: Aaron Date: Sun, 2 Jun 2013 20:54:20 +0200 Subject: Add initial implementation of having erl_tidy print to screen instead of writing to the file provided. The reason for this is that you may want to have an intermediary step between saving the tidied file and using the output. Use-case personally is wanting to integrate erl_tidy into emacs and tidy buffers, overwriting the current file is generally not how this works in emacs with source code tidiers. --- lib/syntax_tools/src/erl_tidy.erl | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_tidy.erl b/lib/syntax_tools/src/erl_tidy.erl index 0c149634f6..8507ff646e 100644 --- a/lib/syntax_tools/src/erl_tidy.erl +++ b/lib/syntax_tools/src/erl_tidy.erl @@ -309,9 +309,15 @@ file_2(Name, Opts) -> true -> ok; false -> - write_module(Tree, Name, Opts1), - ok - end. + case proplists:get_bool(stdout, Opts1) of + true -> + print_module(Tree, Opts1), + ok; + false -> + write_module(Tree, Name, Opts1), + ok + end + end. read_module(Name, Opts) -> verbose("reading module `~ts'.", [filename(Name)], Opts), @@ -399,6 +405,10 @@ write_module(Tree, Name, Opts) -> throw(R) end. +print_module(Tree, Opts) -> + Printer = proplists:get_value(printer, Opts), + io:format(Printer(Tree, Opts)). + output(FD, Printer, Tree, Opts) -> io:put_chars(FD, Printer(Tree, Opts)), io:nl(FD). -- cgit v1.2.3 From a857d28603ad674c40a977249c1b6ae8d29c1f6c Mon Sep 17 00:00:00 2001 From: Aaron Date: Sun, 2 Jun 2013 21:46:25 +0200 Subject: Added documenation for the new option describing what it does. --- lib/syntax_tools/src/erl_tidy.erl | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_tidy.erl b/lib/syntax_tools/src/erl_tidy.erl index 8507ff646e..7444d8dc67 100644 --- a/lib/syntax_tools/src/erl_tidy.erl +++ b/lib/syntax_tools/src/erl_tidy.erl @@ -269,6 +269,13 @@ file(Name) -> %% is typically most useful if the `verbose' flag is enabled, to %% generate reports about the program files without affecting %% them. The default value is `false'. +%% +%%
{stdout, boolean()}
+%% +%%
If the value is `true', instead of the file being written +%% to disk it will be printed to stdout. The default value is +%% `false'.
+%% %% %% %% See the function `module/2' for further options. -- cgit v1.2.3 From 302954c0641ba679fb33a003c0a665b7b4a79a0e Mon Sep 17 00:00:00 2001 From: Lukas Larsson Date: Mon, 27 Jan 2014 16:14:10 +0100 Subject: 17.0 anchor and broken links fixes --- lib/syntax_tools/src/erl_syntax.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl index 82be9aa489..4f7f9e83ac 100644 --- a/lib/syntax_tools/src/erl_syntax.erl +++ b/lib/syntax_tools/src/erl_syntax.erl @@ -512,7 +512,7 @@ %% @see macro/2 %% @see match_expr/2 %% @see module_qualifier/2 -%% @see named_fun_expr/1 +%% @see named_fun_expr/2 %% @see nil/0 %% @see operator/1 %% @see parentheses/1 @@ -5699,7 +5699,7 @@ named_fun_expr_name(Node) -> %% ===================================================================== %% @doc Returns the list of clause subtrees of a `named_fun_expr' node. %% -%% @see named_fun_expr/1 +%% @see named_fun_expr/2 -spec named_fun_expr_clauses(syntaxTree()) -> [syntaxTree()]. @@ -5722,7 +5722,7 @@ named_fun_expr_clauses(Node) -> %% syntax tree `C' of type `clause' such that %% `clause_patterns(C)' is a nonempty list. %% -%% @see named_fun_expr/1 +%% @see named_fun_expr/2 %% @see named_fun_expr_clauses/1 %% @see clause/3 %% @see clause_patterns/1 -- cgit v1.2.3 From b42453915e4c6aa1133e88c06a60dfbc5fe5e1f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn-Egil=20Dahlberg?= Date: Thu, 24 Oct 2013 21:17:11 +0200 Subject: edoc,syntax_tools: Teach Map understanding --- lib/syntax_tools/src/erl_syntax.erl | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl index 4f7f9e83ac..78a37445ed 100644 --- a/lib/syntax_tools/src/erl_syntax.erl +++ b/lib/syntax_tools/src/erl_syntax.erl @@ -582,6 +582,9 @@ type(Node) -> {match, _, _, _} -> match_expr; {op, _, _, _, _} -> infix_expr; {op, _, _, _} -> prefix_expr; + {map,_,_} -> map; + {map_field_assoc,_,_,_} -> map_field_assoc; + {map_field_exact,_,_,_} -> map_field_exact; {record, _, _, _, _} -> record_expr; {record, _, _, _} -> record_expr; {record_field, _, _, _, _} -> record_access; @@ -1909,6 +1912,28 @@ atom_literal(Node) -> io_lib:write_atom(atom_value(Node)). +%% ===================================================================== + +map_elements(Node) -> + case unwrap(Node) of + {map, _, List} -> + List; + Node1 -> + data(Node1) + end. + +map_field_elements({_,_,K,V}) -> + [K,V]. + +map(List) -> + tree(map, List). + +map_field_assoc(List) -> + tree(map_field_assoc, List). + +map_field_exact(List) -> + tree(map_field_exact, List). + %% ===================================================================== %% @doc Creates an abstract tuple. If `Elements' is %% `[X1, ..., Xn]', the result represents @@ -6396,6 +6421,12 @@ subtrees(T) -> try_expr_clauses(T), try_expr_handlers(T), try_expr_after(T)]; + map -> + [map_elements(T)]; + map_field_assoc -> + [map_field_elements(T)]; + map_field_exact -> + [map_field_elements(T)]; tuple -> [tuple_elements(T)] end @@ -6491,7 +6522,10 @@ make_tree(record_index_expr, [[T], [F]]) -> make_tree(rule, [[N], C]) -> rule(N, C); make_tree(size_qualifier, [[N], [A]]) -> size_qualifier(N, A); make_tree(try_expr, [B, C, H, A]) -> try_expr(B, C, H, A); -make_tree(tuple, [E]) -> tuple(E). +make_tree(tuple, [E]) -> tuple(E); +make_tree(map, [E]) -> map(E); +make_tree(map_field_assoc, [E]) -> map_field_assoc(E); +make_tree(map_field_exact, [E]) -> map_field_exact(E). %% ===================================================================== -- cgit v1.2.3 From 1552476d5ba74e177ea9d6b2d2714fd7354a465d Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Tue, 28 Jan 2014 15:29:27 +0100 Subject: Fix representation of named funs in syntax_tools A named fun's name is a variable name, its type in syntax_tools is named_fun_expr. --- lib/syntax_tools/src/erl_syntax.erl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl index 78a37445ed..93187fa018 100644 --- a/lib/syntax_tools/src/erl_syntax.erl +++ b/lib/syntax_tools/src/erl_syntax.erl @@ -5691,15 +5691,15 @@ fun_expr_arity(Node) -> -spec named_fun_expr(syntaxTree(), [syntaxTree()]) -> syntaxTree(). named_fun_expr(Name, Clauses) -> - tree(fun_expr, #named_fun_expr{name = Name, clauses = Clauses}). + tree(named_fun_expr, #named_fun_expr{name = Name, clauses = Clauses}). revert_named_fun_expr(Node) -> Pos = get_pos(Node), Name = named_fun_expr_name(Node), Clauses = [revert_clause(C) || C <- named_fun_expr_clauses(Node)], case type(Name) of - var -> - {named_fun, Pos, concrete(Name), Clauses}; + variable -> + {named_fun, Pos, variable_name(Name), Clauses}; _ -> Node end. @@ -5715,7 +5715,7 @@ revert_named_fun_expr(Node) -> named_fun_expr_name(Node) -> case unwrap(Node) of {named_fun, Pos, Name, _} -> - set_pos(atom(Name), Pos); + set_pos(variable(Name), Pos); Node1 -> (data(Node1))#named_fun_expr.name end. -- cgit v1.2.3 From 53a9a250470c29334a07dddafb6b1adcde39596b Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Fri, 31 Jan 2014 20:59:27 +0100 Subject: Complete support of maps in erl_syntax map_expr/1 map_expr/2 map_expr_argument/1 map_expr_fields/1 map_field_assoc/2 map_field_assoc_name/1 map_field_assoc_value/1 map_field_exact/2 map_field_exact_name/1 map_field_exact_value/1 --- lib/syntax_tools/src/erl_syntax.erl | 257 ++++++++++++++++++++++++++++++++---- 1 file changed, 231 insertions(+), 26 deletions(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl index 93187fa018..2f4c9ac309 100644 --- a/lib/syntax_tools/src/erl_syntax.erl +++ b/lib/syntax_tools/src/erl_syntax.erl @@ -220,6 +220,16 @@ macro/2, macro_arguments/1, macro_name/1, + map_expr/1, + map_expr/2, + map_expr_argument/1, + map_expr_fields/1, + map_field_assoc/2, + map_field_assoc_name/1, + map_field_assoc_value/1, + map_field_exact/2, + map_field_exact_name/1, + map_field_exact_value/1, match_expr/2, match_expr_body/1, match_expr_pattern/1, @@ -580,11 +590,12 @@ type(Node) -> {lc, _, _, _} -> list_comp; {bc, _, _, _} -> binary_comp; {match, _, _, _} -> match_expr; + {map, _, _, _} -> map_expr; + {map, _, _} -> map_expr; + {map_field_assoc, _, _, _} -> map_field_assoc; + {map_field_exact, _, _, _} -> map_field_exact; {op, _, _, _, _} -> infix_expr; {op, _, _, _} -> prefix_expr; - {map,_,_} -> map; - {map_field_assoc,_,_,_} -> map_field_assoc; - {map_field_exact,_,_,_} -> map_field_exact; {record, _, _, _, _} -> record_expr; {record, _, _, _} -> record_expr; {record_field, _, _, _, _} -> record_access; @@ -1913,26 +1924,206 @@ atom_literal(Node) -> %% ===================================================================== +%% @equiv map_expr(none, Fields) -map_elements(Node) -> +-spec map_expr([syntaxTree()]) -> syntaxTree(). + +map_expr(Fields) -> + map_expr(none, Fields). + + +%% ===================================================================== +%% @doc Creates an abstract map expression. If `Fields' is +%% `[F1, ..., Fn]', then if `Argument' is `none', the result represents +%% "#{F1, ..., Fn}", +%% otherwise it represents +%% "Argument#{F1, ..., Fn}". +%% +%% @see map_expr/1 +%% @see map_expr_argument/1 +%% @see map_expr_fields/1 +%% @see map_field_assoc/2 +%% @see map_field_exact/2 + +-record(map_expr, {argument :: 'none' | syntaxTree(), + fields :: [syntaxTree()]}). + +%% `erl_parse' representation: +%% +%% {map, Pos, Fields} +%% {map, Pos, Argument, Fields} + +-spec map_expr('none' | syntaxTree(), [syntaxTree()]) -> syntaxTree(). + +map_expr(Argument, Fields) -> + tree(map_expr, #map_expr{argument = Argument, fields = Fields}). + +revert_map_expr(Node) -> + Pos = get_pos(Node), + Argument = map_expr_argument(Node), + Fields = map_expr_fields(Node), + case Argument of + none -> + {map, Pos, Fields}; + _ -> + {map, Pos, Argument, Fields} + end. + + +%% ===================================================================== +%% @doc Returns the argument subtree of a `map_expr' node, if any. If `Node' +%% represents "#{...}", `none' is returned. +%% Otherwise, if `Node' represents "Argument#{...}", +%% `Argument' is returned. +%% +%% @see map_expr/3 + +-spec map_expr_argument(syntaxTree()) -> 'none' | syntaxTree(). + +map_expr_argument(Node) -> case unwrap(Node) of - {map, _, List} -> - List; - Node1 -> - data(Node1) + {map, _, _} -> + none; + {map, _, Argument, _} -> + Argument; + Node1 -> + (data(Node1))#map_expr.argument end. -map_field_elements({_,_,K,V}) -> - [K,V]. -map(List) -> - tree(map, List). +%% ===================================================================== +%% @doc Returns the list of field subtrees of a `map_expr' node. +%% +%% @see map_expr/3 + +-spec map_expr_fields(syntaxTree()) -> [syntaxTree()]. + +map_expr_fields(Node) -> + case unwrap(Node) of + {map, _, Fields} -> + Fields; + {map, _, _, Fields} -> + Fields; + Node1 -> + (data(Node1))#map_expr.fields + end. + -map_field_assoc(List) -> - tree(map_field_assoc, List). +%% ===================================================================== +%% @doc Creates an abstract map assoc field. The result represents +%% "Name => Value". +%% +%% @see map_field_assoc_name/1 +%% @see map_field_assoc_value/1 +%% @see map_expr/3 + +-record(map_field_assoc, {name :: syntaxTree(), value :: syntaxTree()}). + +%% `erl_parse' representation: +%% +%% {map_field_assoc, Pos, Name, Value} + +-spec map_field_assoc(syntaxTree(), syntaxTree()) -> syntaxTree(). + +map_field_assoc(Name, Value) -> + tree(map_field_assoc, #map_field_assoc{name = Name, value = Value}). + +revert_map_field_assoc(Node) -> + Pos = get_pos(Node), + Name = map_field_assoc_name(Node), + Value = map_field_assoc_value(Node), + {map_field_assoc, Pos, Name, Value}. + + +%% ===================================================================== +%% @doc Returns the name subtree of a `map_field_assoc' node. +%% +%% @see map_field_assoc/2 + +-spec map_field_assoc_name(syntaxTree()) -> syntaxTree(). + +map_field_assoc_name(Node) -> + case Node of + {map_field_assoc, _, Name, _} -> + Name; + _ -> + (data(Node))#map_field_assoc.name + end. + + +%% ===================================================================== +%% @doc Returns the value subtree of a `map_field_assoc' node. +%% +%% @see map_field_assoc/2 + +-spec map_field_assoc_value(syntaxTree()) -> syntaxTree(). + +map_field_assoc_value(Node) -> + case Node of + {map_field_assoc, _, _, Value} -> + Value; + _ -> + (data(Node))#map_field_assoc.name + end. + + +%% ===================================================================== +%% @doc Creates an abstract map exact field. The result represents +%% "Name := Value". +%% +%% @see map_field_exact_name/1 +%% @see map_field_exact_value/1 +%% @see map_expr/3 + +-record(map_field_exact, {name :: syntaxTree(), value :: syntaxTree()}). + +%% `erl_parse' representation: +%% +%% {map_field_exact, Pos, Name, Value} + +-spec map_field_exact(syntaxTree(), syntaxTree()) -> syntaxTree(). + +map_field_exact(Name, Value) -> + tree(map_field_exact, #map_field_exact{name = Name, value = Value}). + +revert_map_field_exact(Node) -> + Pos = get_pos(Node), + Name = map_field_exact_name(Node), + Value = map_field_exact_value(Node), + {map_field_exact, Pos, Name, Value}. + + +%% ===================================================================== +%% @doc Returns the name subtree of a `map_field_exact' node. +%% +%% @see map_field_exact/2 + +-spec map_field_exact_name(syntaxTree()) -> syntaxTree(). + +map_field_exact_name(Node) -> + case Node of + {map_field_exact, _, Name, _} -> + Name; + _ -> + (data(Node))#map_field_exact.name + end. + + +%% ===================================================================== +%% @doc Returns the value subtree of a `map_field_exact' node. +%% +%% @see map_field_exact/2 + +-spec map_field_exact_value(syntaxTree()) -> syntaxTree(). + +map_field_exact_value(Node) -> + case Node of + {map_field_exact, _, _, Value} -> + Value; + _ -> + (data(Node))#map_field_exact.name + end. -map_field_exact(List) -> - tree(map_field_exact, List). %% ===================================================================== %% @doc Creates an abstract tuple. If `Elements' is @@ -6117,6 +6308,12 @@ revert_root(Node) -> revert_list(Node); list_comp -> revert_list_comp(Node); + map_expr -> + revert_map_expr(Node); + map_field_assoc -> + revert_map_field_assoc(Node); + map_field_exact -> + revert_map_field_exact(Node); match_expr -> revert_match_expr(Node); module_qualifier -> @@ -6358,6 +6555,19 @@ subtrees(T) -> As -> [[macro_name(T)], As] end; + map_expr -> + case map_expr_argument(T) of + none -> + [map_expr_fields(T)]; + V -> + [[V], map_expr_fields(T)] + end; + map_field_assoc -> + [[map_field_assoc_name(T)], + [map_field_assoc_value(T)]]; + map_field_exact -> + [[map_field_exact_name(T)], + [map_field_exact_value(T)]]; match_expr -> [[match_expr_pattern(T)], [match_expr_body(T)]]; @@ -6421,12 +6631,6 @@ subtrees(T) -> try_expr_clauses(T), try_expr_handlers(T), try_expr_after(T)]; - map -> - [map_elements(T)]; - map_field_assoc -> - [map_field_elements(T)]; - map_field_exact -> - [map_field_elements(T)]; tuple -> [tuple_elements(T)] end @@ -6502,6 +6706,10 @@ make_tree(list, [P, [S]]) -> list(P, S); make_tree(list_comp, [[T], B]) -> list_comp(T, B); make_tree(macro, [[N]]) -> macro(N); make_tree(macro, [[N], A]) -> macro(N, A); +make_tree(map_expr, [Fs]) -> map_expr(Fs); +make_tree(map_expr, [[E], Fs]) -> map_expr(E, Fs); +make_tree(map_field_assoc, [[K], [V]]) -> map_field_assoc(K, V); +make_tree(map_field_exact, [[K], [V]]) -> map_field_exact(K, V); make_tree(match_expr, [[P], [E]]) -> match_expr(P, E); make_tree(named_fun_expr, [[N], C]) -> named_fun_expr(N, C); make_tree(module_qualifier, [[M], [N]]) -> module_qualifier(M, N); @@ -6522,10 +6730,7 @@ make_tree(record_index_expr, [[T], [F]]) -> make_tree(rule, [[N], C]) -> rule(N, C); make_tree(size_qualifier, [[N], [A]]) -> size_qualifier(N, A); make_tree(try_expr, [B, C, H, A]) -> try_expr(B, C, H, A); -make_tree(tuple, [E]) -> tuple(E); -make_tree(map, [E]) -> map(E); -make_tree(map_field_assoc, [E]) -> map_field_assoc(E); -make_tree(map_field_exact, [E]) -> map_field_exact(E). +make_tree(tuple, [E]) -> tuple(E). %% ===================================================================== -- cgit v1.2.3 From 52f88fc986a6ad4985cc0343ccc034ab3bd693fa Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Sat, 1 Feb 2014 04:03:32 +0100 Subject: Support named funs in erl_prettypr --- lib/syntax_tools/src/erl_prettypr.erl | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_prettypr.erl b/lib/syntax_tools/src/erl_prettypr.erl index 1ffcf31134..563fd36fdf 100644 --- a/lib/syntax_tools/src/erl_prettypr.erl +++ b/lib/syntax_tools/src/erl_prettypr.erl @@ -637,6 +637,14 @@ lay_2(Node, Ctxt) -> sep([follow(text("fun"), D, Ctxt1#ctxt.sub_indent), text("end")]); + named_fun_expr -> + Ctxt1 = reset_prec(Ctxt), + D1 = lay(erl_syntax:named_fun_expr_name(Node), Ctxt1), + D = lay_clauses(erl_syntax:named_fun_expr_clauses(Node), + {function,D1}, Ctxt1), + sep([follow(text("fun"), D, Ctxt1#ctxt.sub_indent), + text("end")]); + module_qualifier -> {PrecL, _Prec, PrecR} = inop_prec(':'), D1 = lay(erl_syntax:module_qualifier_argument(Node), -- cgit v1.2.3 From 81824d20efb18b08d53aa5bb7e1c764074f473e7 Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Sat, 1 Feb 2014 04:20:00 +0100 Subject: Support maps in erl_prettypr --- lib/syntax_tools/src/erl_prettypr.erl | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_prettypr.erl b/lib/syntax_tools/src/erl_prettypr.erl index 1ffcf31134..4d6e88f58b 100644 --- a/lib/syntax_tools/src/erl_prettypr.erl +++ b/lib/syntax_tools/src/erl_prettypr.erl @@ -892,6 +892,32 @@ lay_2(Node, Ctxt) -> beside(floating(text(".")), D2)), maybe_parentheses(D3, Prec, Ctxt); + map_expr -> + {PrecL, Prec, _} = inop_prec('#'), + Ctxt1 = reset_prec(Ctxt), + D1 = par(seq(erl_syntax:map_expr_fields(Node), + floating(text(",")), Ctxt1, fun lay/2)), + D2 = beside(text("#{"), beside(D1, floating(text("}")))), + D3 = case erl_syntax:map_expr_argument(Node) of + none -> + D2; + A -> + beside(lay(A, set_prec(Ctxt, PrecL)), D2) + end, + maybe_parentheses(D3, Prec, Ctxt); + + map_field_assoc -> + Ctxt1 = reset_prec(Ctxt), + D1 = lay(erl_syntax:map_field_assoc_name(Node), Ctxt1), + D2 = lay(erl_syntax:map_field_assoc_value(Node), Ctxt1), + par([D1, floating(text("=>")), D2], Ctxt1#ctxt.break_indent); + + map_field_exact -> + Ctxt1 = reset_prec(Ctxt), + D1 = lay(erl_syntax:map_field_exact_name(Node), Ctxt1), + D2 = lay(erl_syntax:map_field_exact_value(Node), Ctxt1), + par([D1, floating(text(":=")), D2], Ctxt1#ctxt.break_indent); + rule -> %% Comments on the name will be repeated; cf. %% `function'. -- cgit v1.2.3 From 3a511021e7d0a720b222d61a255352bd261671c0 Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Sat, 8 Feb 2014 17:49:35 +0100 Subject: Mention map trees in erl_syntax:type/1 documentation --- lib/syntax_tools/src/erl_syntax.erl | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl index 2f4c9ac309..ed9decd4b5 100644 --- a/lib/syntax_tools/src/erl_syntax.erl +++ b/lib/syntax_tools/src/erl_syntax.erl @@ -453,33 +453,38 @@ %% %% list_comp %% macro +%% map_expr +%% map_field_assoc +%% +%% map_field_exact %% match_expr %% module_qualifier -%% %% named_fun_expr +%% %% nil %% operator %% parentheses -%% %% prefix_expr +%% %% receive_expr %% record_access %% record_expr -%% %% record_field +%% %% record_index_expr %% rule %% size_qualifier -%% %% string +%% %% text %% try_expr %% tuple -%% %% underscore +%% %% variable %% warning_marker %% +%% %% %% %% @@ -520,6 +525,9 @@ %% @see list/2 %% @see list_comp/2 %% @see macro/2 +%% @see map_expr/2 +%% @see map_field_assoc/2 +%% @see map_field_exact/2 %% @see match_expr/2 %% @see module_qualifier/2 %% @see named_fun_expr/2 -- cgit v1.2.3 From a61f66e1157b0e77839b76ef416f436f28304579 Mon Sep 17 00:00:00 2001 From: Tobias Schlager Date: Tue, 18 Feb 2014 12:29:01 +0100 Subject: Fix library application appup files As discussed in issue #240 *all* OTP library applications use the '.*' wildcard as up and down version. This makes library applications always up- and downgradeable. Using the wildcard version obsoletes all maintenance tasks regarding library applications' appup files. Additionally, it prevents upgrade problems caused by automatically included application dependencies when using reltool to create releases. Missing copyright headers are now consistently present. --- lib/syntax_tools/src/syntax_tools.appup.src | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/syntax_tools.appup.src b/lib/syntax_tools/src/syntax_tools.appup.src index 54a63833e6..89c25d14d7 100644 --- a/lib/syntax_tools/src/syntax_tools.appup.src +++ b/lib/syntax_tools/src/syntax_tools.appup.src @@ -1 +1,21 @@ -{"%VSN%",[],[]}. +%% -*- erlang -*- +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2014. 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% +{"%VSN%", + [{<<".*">>,[{restart_application, syntax_tools}]}], + [{<<".*">>,[{restart_application, syntax_tools}]}] +}. -- cgit v1.2.3 From b66e75c285cba469c5225f3394da149456d17d16 Mon Sep 17 00:00:00 2001 From: Hans Bolinder Date: Mon, 3 Feb 2014 10:27:39 +0100 Subject: Deprecate pre-defined built-in types The types array(), dict(), digraph(), gb_set(), gb_tree(), queue(), set(), and tid() have been deprecated. They will be removed in OTP 18.0. Instead the types array:array(), dict:dict(), digraph:graph(), gb_set:set(), gb_tree:tree(), queue:queue(), sets:set(), and ets:tid() can be used. (Note: it has always been necessary to use ets:tid().) It is allowed in OTP 17.0 to locally re-define the types array(), dict(), and so on. New types array:array/1, dict:dict/2, gb_sets:set/1, gb_trees:tree/2, queue:queue/1, and sets:set/1 have been added. --- lib/syntax_tools/src/erl_syntax_lib.erl | 10 +++++----- lib/syntax_tools/src/erl_tidy.erl | 14 +++++++------- lib/syntax_tools/src/igor.erl | 20 ++++++++++---------- 3 files changed, 22 insertions(+), 22 deletions(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_syntax_lib.erl b/lib/syntax_tools/src/erl_syntax_lib.erl index e4665b99fc..2f0488abec 100644 --- a/lib/syntax_tools/src/erl_syntax_lib.erl +++ b/lib/syntax_tools/src/erl_syntax_lib.erl @@ -288,7 +288,7 @@ mapfoldl(_, S, []) -> %% %% @see //stdlib/sets --spec variables(erl_syntax:syntaxTree()) -> set(). +-spec variables(erl_syntax:syntaxTree()) -> sets:set(atom()). variables(Tree) -> variables(Tree, sets:new()). @@ -343,7 +343,7 @@ default_variable_name(N) -> %% %% @see new_variable_name/2 --spec new_variable_name(set()) -> atom(). +-spec new_variable_name(sets:set(atom())) -> atom(). new_variable_name(S) -> new_variable_name(fun default_variable_name/1, S). @@ -369,7 +369,7 @@ new_variable_name(S) -> %% @see //stdlib/sets %% @see //stdlib/random --spec new_variable_name(fun((integer()) -> atom()), set()) -> atom(). +-spec new_variable_name(fun((integer()) -> atom()), sets:set(atom())) -> atom(). new_variable_name(F, S) -> R = start_range(S), @@ -416,7 +416,7 @@ generate(_Key, Range) -> %% %% @see new_variable_name/1 --spec new_variable_names(integer(), set()) -> [atom()]. +-spec new_variable_names(integer(), sets:set(atom())) -> [atom()]. new_variable_names(N, S) -> new_variable_names(N, fun default_variable_name/1, S). @@ -432,7 +432,7 @@ new_variable_names(N, S) -> %% %% @see new_variable_name/2 --spec new_variable_names(integer(), fun((integer()) -> atom()), set()) -> +-spec new_variable_names(integer(), fun((integer()) -> atom()), sets:set(atom())) -> [atom()]. new_variable_names(N, F, S) when is_integer(N) -> diff --git a/lib/syntax_tools/src/erl_tidy.erl b/lib/syntax_tools/src/erl_tidy.erl index 7444d8dc67..38e0c2099b 100644 --- a/lib/syntax_tools/src/erl_tidy.erl +++ b/lib/syntax_tools/src/erl_tidy.erl @@ -14,7 +14,7 @@ %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 %% USA %% -%% @copyright 1999-2006 Richard Carlsson +%% @copyright 1999-2014 Richard Carlsson %% @author Richard Carlsson %% @end %% ===================================================================== @@ -958,7 +958,7 @@ hidden_uses_2(Tree, Used) -> -record(env, {file :: file:filename(), module :: atom(), current :: fa(), - imports = dict:new() :: dict(), + imports = dict:new() :: dict:dict(atom(), atom()), context = normal :: context(), verbosity = 1 :: 0 | 1 | 2, quiet = false :: boolean(), @@ -970,12 +970,12 @@ hidden_uses_2(Tree, Used) -> old_guard_tests = false :: boolean()}). -record(st, {varc :: non_neg_integer(), - used = sets:new() :: set(), - imported :: set(), - vars :: set(), - functions :: set(), + used = sets:new() :: sets:set({atom(), arity()}), + imported :: sets:set({atom(), arity()}), + vars :: sets:set(atom()), + functions :: sets:set({atom(), arity()}), new_forms = [] :: [erl_syntax:syntaxTree()], - rename :: dict()}). + rename :: dict:dict(mfa(), {atom(), atom()})}). visit_used(Names, Defs, Roots, Imports, Module, Opts) -> File = proplists:get_value(file, Opts, ""), diff --git a/lib/syntax_tools/src/igor.erl b/lib/syntax_tools/src/igor.erl index 19b1cd592f..e6aff7b20a 100644 --- a/lib/syntax_tools/src/igor.erl +++ b/lib/syntax_tools/src/igor.erl @@ -14,7 +14,7 @@ %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 %% USA %% -%% @copyright 1998-2006 Richard Carlsson +%% @copyright 1998-2014 Richard Carlsson %% @author Richard Carlsson %% @end %% ===================================================================== @@ -695,7 +695,7 @@ merge_files1(Files, Opts) -> preserved :: boolean(), no_headers :: boolean(), notes :: notes(), - redirect :: dict(), % = dict(atom(), atom()) + redirect :: dict:dict(atom(), atom()), no_imports :: ordsets:ordset(atom()), options :: [option()] }). @@ -727,7 +727,7 @@ merge_sources(Name, Sources, Opts) -> %% Data structure for keeping state during transformation. --record(state, {export :: set()}). +-record(state, {export :: sets:set(atom(), arity())}). state__add_export(Name, Arity, S) -> S#state{export = sets:add_element({Name, Arity}, @@ -1039,7 +1039,7 @@ make_stub(M, Map, Env) -> -type atts() :: 'delete' | 'kill'. -type file_atts() :: 'delete' | 'keep' | 'kill'. --record(filter, {records :: set(), +-record(filter, {records :: sets:set(atom()), file_attributes :: file_atts(), attributes :: atts()}). @@ -1588,17 +1588,17 @@ alias_expansions_2(Modules, Table) -> -record(code, {module :: atom(), target :: atom(), - sources :: set(), % set(atom()), - static :: set(), % set(atom()), - safe :: set(), % set(atom()), + sources :: sets:set(atom()), + static :: sets:set(atom()), + safe :: sets:set(atom()), preserved :: boolean(), no_headers :: boolean(), notes :: notes(), map :: map_fun(), renaming :: fun((atom()) -> map_fun()), - expand :: dict(), % = dict({atom(), integer()}, - % {atom(), {atom(), integer()}}) - redirect :: dict() % = dict(atom(), atom()) + expand :: dict:dict({atom(), integer()}, + {atom(), {atom(), integer()}}), + redirect :: dict:dict(atom(), atom()) }). %% `Trees' must be a list of syntax trees of type `form_list'. The -- cgit v1.2.3 From b473dd94321157f7c2e9b09769bd44dcb9f8bbf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn-Egil=20Dahlberg?= Date: Mon, 24 Feb 2014 15:44:23 +0100 Subject: syntax_tools: Fix broken doc links for Maps --- lib/syntax_tools/src/erl_syntax.erl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl index ed9decd4b5..c9996c954e 100644 --- a/lib/syntax_tools/src/erl_syntax.erl +++ b/lib/syntax_tools/src/erl_syntax.erl @@ -1984,7 +1984,7 @@ revert_map_expr(Node) -> %% Otherwise, if `Node' represents "Argument#{...}", %% `Argument' is returned. %% -%% @see map_expr/3 +%% @see map_expr/2 -spec map_expr_argument(syntaxTree()) -> 'none' | syntaxTree(). @@ -2002,7 +2002,7 @@ map_expr_argument(Node) -> %% ===================================================================== %% @doc Returns the list of field subtrees of a `map_expr' node. %% -%% @see map_expr/3 +%% @see map_expr/2 -spec map_expr_fields(syntaxTree()) -> [syntaxTree()]. @@ -2023,7 +2023,7 @@ map_expr_fields(Node) -> %% %% @see map_field_assoc_name/1 %% @see map_field_assoc_value/1 -%% @see map_expr/3 +%% @see map_expr/2 -record(map_field_assoc, {name :: syntaxTree(), value :: syntaxTree()}). @@ -2081,7 +2081,7 @@ map_field_assoc_value(Node) -> %% %% @see map_field_exact_name/1 %% @see map_field_exact_value/1 -%% @see map_expr/3 +%% @see map_expr/2 -record(map_field_exact, {name :: syntaxTree(), value :: syntaxTree()}). -- cgit v1.2.3 From 21227c80407b4c4eedb0eacc44671a78e9df43b5 Mon Sep 17 00:00:00 2001 From: Hans Bolinder Date: Tue, 25 Feb 2014 09:54:00 +0100 Subject: Correct a contract in the igor module Thanks Kostis. --- lib/syntax_tools/src/igor.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/igor.erl b/lib/syntax_tools/src/igor.erl index e6aff7b20a..0420508f2a 100644 --- a/lib/syntax_tools/src/igor.erl +++ b/lib/syntax_tools/src/igor.erl @@ -727,7 +727,7 @@ merge_sources(Name, Sources, Opts) -> %% Data structure for keeping state during transformation. --record(state, {export :: sets:set(atom(), arity())}). +-record(state, {export :: sets:set({atom(), arity()})}). state__add_export(Name, Arity, S) -> S#state{export = sets:add_element({Name, Arity}, -- cgit v1.2.3 From 22ff87a0c8db877e3ce53b6ff915dcc6a75c5c0c Mon Sep 17 00:00:00 2001 From: Rickard Green Date: Mon, 10 Mar 2014 17:15:38 +0100 Subject: Introduce runtime_dependencies in .app files Most dependencies introduced are exactly the dependencies to other applications found by xref. That is, there might be real dependencies missing. There might also be pure debug dependencies listed that probably should be removed. Each application has to be manually inspected in order to ensure that all real dependencies are listed. All dependencies introduced are to application versions used in OTP 17.0. This since the previously used version scheme wasn't designed for this, and in order to minimize the work of introducing the dependencies. --- lib/syntax_tools/src/syntax_tools.app.src | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/syntax_tools.app.src b/lib/syntax_tools/src/syntax_tools.app.src index dc0b9edd62..83dcb5fe23 100644 --- a/lib/syntax_tools/src/syntax_tools.app.src +++ b/lib/syntax_tools/src/syntax_tools.app.src @@ -14,4 +14,5 @@ prettypr]}, {registered,[]}, {applications, [stdlib]}, - {env, []}]}. + {env, []}, + {runtime_dependencies, ["stdlib-2.0","kernel-3.0","erts-6.0"]}]}. -- cgit v1.2.3 From 146260727638e8a477aeda7828364ce45dc506a0 Mon Sep 17 00:00:00 2001 From: Hans Bolinder Date: Tue, 15 Apr 2014 09:38:41 +0200 Subject: Introduce the attribute -optional_callbacks in the context of behaviours --- lib/syntax_tools/src/erl_syntax.erl | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl index c9996c954e..f7819c2662 100644 --- a/lib/syntax_tools/src/erl_syntax.erl +++ b/lib/syntax_tools/src/erl_syntax.erl @@ -3320,6 +3320,11 @@ attribute_arguments(Node) -> [set_pos( list(unfold_function_names(Data, Pos)), Pos)]; + optional_callbacks -> + D = try list(unfold_function_names(Data, Pos)) + catch _:_ -> abstract(Data) + end, + [set_pos(D, Pos)]; import -> {Module, Imports} = Data, [set_pos(atom(Module), Pos), -- cgit v1.2.3 From 15c6bea32b05ea1eca612c7637fc3df133937059 Mon Sep 17 00:00:00 2001 From: Hans Bolinder Date: Tue, 22 Apr 2014 14:37:00 +0200 Subject: [syntax_tools] Let erl_syntax:concrete() accept the F/A syntax Note: `arity_qualifier` nodes are recognized. This is to follow The Erlang Parser when it comes to wild attributes: both {F, A} and F/A are recognized, which makes it possible to turn wild attributes into recognized attributes without at the same time making it impossible to compile files using the new syntax with the old version of the Erlang Compiler. --- lib/syntax_tools/src/erl_syntax.erl | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl index f7819c2662..2576d227df 100644 --- a/lib/syntax_tools/src/erl_syntax.erl +++ b/lib/syntax_tools/src/erl_syntax.erl @@ -6134,6 +6134,13 @@ abstract_tail(H, T) -> %% {@link char/1} function to explicitly create an abstract %% character.) %% +%% Note: `arity_qualifier' nodes are recognized. This is to follow The +%% Erlang Parser when it comes to wild attributes: both {F, A} and F/A +%% are recognized, which makes it possible to turn wild attributes +%% into recognized attributes without at the same time making it +%% impossible to compile files using the new syntax with the old +%% version of the Erlang Compiler. +%% %% @see abstract/1 %% @see is_literal/1 %% @see char/1 @@ -6175,6 +6182,20 @@ concrete(Node) -> {value, concrete(F), []} end, [], true), B; + arity_qualifier -> + A = erl_syntax:arity_qualifier_argument(Node), + case erl_syntax:type(A) of + integer -> + F = erl_syntax:arity_qualifier_body(Node), + case erl_syntax:type(F) of + atom -> + {F, A}; + _ -> + erlang:error({badarg, Node}) + end; + _ -> + erlang:error({badarg, Node}) + end; _ -> erlang:error({badarg, Node}) end. -- cgit v1.2.3 From c8695cd2b0fcd95f0af5c67bf049d19dbba5ec82 Mon Sep 17 00:00:00 2001 From: Roland Karlsson Date: Mon, 21 Apr 2014 13:50:32 +0200 Subject: Fix reverting map in syntax_tools There was a copy-paste bug in erl_syntax when running e.g. erl_syntax:revert_forms, affecting maps. Instead of getting Key/Value you got Key/Key in the resulting abstract form. --- lib/syntax_tools/src/erl_syntax.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl index c9996c954e..46a5ca48df 100644 --- a/lib/syntax_tools/src/erl_syntax.erl +++ b/lib/syntax_tools/src/erl_syntax.erl @@ -2071,7 +2071,7 @@ map_field_assoc_value(Node) -> {map_field_assoc, _, _, Value} -> Value; _ -> - (data(Node))#map_field_assoc.name + (data(Node))#map_field_assoc.value end. @@ -2129,7 +2129,7 @@ map_field_exact_value(Node) -> {map_field_exact, _, _, Value} -> Value; _ -> - (data(Node))#map_field_exact.name + (data(Node))#map_field_exact.value end. -- cgit v1.2.3 From de3774d93b061a54d33c7c58f07c69cbbb36cadf Mon Sep 17 00:00:00 2001 From: Hans Bolinder Date: Wed, 25 Jun 2014 15:26:45 +0200 Subject: edoc, syntax_tools: Don't fail on invalid UTF-8 As a temporary measure to ease the transition to default UTF-8 encoding, automatically fall back to the Latin-1 encoding (without any warnings; the Erlang Compiler will emit a proper warning). The intention is to remove this workaround in OTP 18 or 19. --- lib/syntax_tools/src/epp_dodger.erl | 28 +++++++++++++++++++++++++++- lib/syntax_tools/src/erl_comment_scan.erl | 13 ++++++++++++- 2 files changed, 39 insertions(+), 2 deletions(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/epp_dodger.erl b/lib/syntax_tools/src/epp_dodger.erl index 131be4e8e4..7e12eab1b5 100644 --- a/lib/syntax_tools/src/epp_dodger.erl +++ b/lib/syntax_tools/src/epp_dodger.erl @@ -184,9 +184,27 @@ quick_parse_file(File, Options) -> parse_file(File, fun quick_parse/3, Options ++ [no_fail]). parse_file(File, Parser, Options) -> + case do_parse_file(utf8, File, Parser, Options) of + {ok, Forms}=Ret -> + case find_invalid_unicode(Forms) of + none -> + Ret; + invalid_unicode -> + case epp:read_encoding(File) of + utf8 -> + Ret; + _ -> + do_parse_file(latin1, File, Parser, Options) + end + end; + Else -> + Else + end. + +do_parse_file(DefEncoding, File, Parser, Options) -> case file:open(File, [read]) of {ok, Dev} -> - _ = epp:set_encoding(Dev), + _ = epp:set_encoding(Dev, DefEncoding), try Parser(Dev, 1, Options) after ok = file:close(Dev) end; @@ -194,6 +212,14 @@ parse_file(File, Parser, Options) -> Error end. +find_invalid_unicode([H|T]) -> + case H of + {error, {_Line, file_io_server, invalid_unicode}} -> + invalid_unicode; + _Other -> + find_invalid_unicode(T) + end; +find_invalid_unicode([]) -> none. %% ===================================================================== %% @spec parse(IODevice) -> {ok, Forms} | {error, errorinfo()} diff --git a/lib/syntax_tools/src/erl_comment_scan.erl b/lib/syntax_tools/src/erl_comment_scan.erl index dae7530ce7..03429d4d42 100644 --- a/lib/syntax_tools/src/erl_comment_scan.erl +++ b/lib/syntax_tools/src/erl_comment_scan.erl @@ -72,13 +72,24 @@ file(Name) -> {ok, V} -> case V of {ok, B} -> - Enc = case epp:read_encoding(Name) of + Encoding = epp:read_encoding_from_binary(B), + Enc = case Encoding of none -> epp:default_encoding(); Enc0 -> Enc0 end, case catch unicode:characters_to_list(B, Enc) of String when is_list(String) -> string(String); + R when Encoding =:= none -> + case + catch unicode:characters_to_list(B, latin1) + of + String when is_list(String) -> + string(String); + _ -> + error_read_file(Name1), + exit(R) + end; R -> error_read_file(Name1), exit(R) -- cgit v1.2.3 From 58d64b9bd9a5759451f678e8a1dcf36b8ded19fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn-Egil=20Dahlberg?= Date: Thu, 23 Oct 2014 18:44:15 +0200 Subject: syntax_tools: Fix is_literal/1 for binary --- lib/syntax_tools/src/erl_syntax.erl | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl index 46a5ca48df..5da53113e0 100644 --- a/lib/syntax_tools/src/erl_syntax.erl +++ b/lib/syntax_tools/src/erl_syntax.erl @@ -6209,10 +6209,17 @@ is_literal(T) -> is_literal(list_head(T)) andalso is_literal(list_tail(T)); tuple -> lists:all(fun is_literal/1, tuple_elements(T)); + binary -> + lists:all(fun is_literal_binary_field/1, binary_fields(T)); _ -> false end. +is_literal_binary_field(F) -> + case binary_field_types(F) of + [] -> is_literal(binary_field_body(F)); + _ -> false + end. %% ===================================================================== %% @doc Returns an `erl_parse'-compatible representation of a -- cgit v1.2.3 From ac264cba1435bf84eecc6b31809bd996c4c8e9c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn-Egil=20Dahlberg?= Date: Thu, 23 Oct 2014 19:49:12 +0200 Subject: syntax_tools: Teach Maps to erl_syntax Affected functions: * erl_syntax:abstract/1 * erl_syntax:concrete/1 * erl_syntax:is_leaf/1 * erl_syntax:is_literal/1 --- lib/syntax_tools/src/erl_syntax.erl | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl index 5da53113e0..40372a2106 100644 --- a/lib/syntax_tools/src/erl_syntax.erl +++ b/lib/syntax_tools/src/erl_syntax.erl @@ -669,6 +669,9 @@ is_leaf(Node) -> operator -> true; % nonstandard type string -> true; text -> true; % nonstandard type + map_expr -> + map_expr_fields(Node) =:= [] andalso + map_expr_argument(Node) =:= none; tuple -> tuple_elements(Node) =:= []; underscore -> true; variable -> true; @@ -6093,6 +6096,9 @@ abstract([]) -> nil(); abstract(T) when is_tuple(T) -> tuple(abstract_list(tuple_to_list(T))); +abstract(T) when is_map(T) -> + map_expr([map_field_assoc(abstract(Key),abstract(Value)) + || {Key,Value} <- maps:to_list(T)]); abstract(T) when is_binary(T) -> binary([binary_field(integer(B)) || B <- binary_to_list(T)]); abstract(T) -> @@ -6154,6 +6160,14 @@ concrete(Node) -> | concrete(list_tail(Node))]; tuple -> list_to_tuple(concrete_list(tuple_elements(Node))); + map_expr -> + As = [tuple([map_field_assoc_name(F), + map_field_assoc_value(F)]) || F <- map_expr_fields(Node)], + M0 = maps:from_list(concrete_list(As)), + case map_expr_argument(Node) of + none -> M0; + Node0 -> maps:merge(concrete(Node0),M0) + end; binary -> Fs = [revert_binary_field( binary_field(binary_field_body(F), @@ -6209,6 +6223,11 @@ is_literal(T) -> is_literal(list_head(T)) andalso is_literal(list_tail(T)); tuple -> lists:all(fun is_literal/1, tuple_elements(T)); + map_expr -> + case map_expr_argument(T) of + none -> true; + Arg -> is_literal(Arg) + end andalso lists:all(fun is_literal_map_field/1, map_expr_fields(T)); binary -> lists:all(fun is_literal_binary_field/1, binary_fields(T)); _ -> @@ -6221,6 +6240,15 @@ is_literal_binary_field(F) -> _ -> false end. +is_literal_map_field(F) -> + case type(F) of + map_field_assoc -> + is_literal(map_field_assoc_name(F)) andalso + is_literal(map_field_assoc_value(F)); + map_field_exact -> + false + end. + %% ===================================================================== %% @doc Returns an `erl_parse'-compatible representation of a %% syntax tree, if possible. If `Tree' represents a -- cgit v1.2.3 From 7bafc1b2ddd34b5905b9ef7d88dee5d8fc9c6aab Mon Sep 17 00:00:00 2001 From: Richard Carlsson Date: Mon, 21 Apr 2014 10:40:12 +0200 Subject: Remove support for mnemosyne-style record field access (The support in erl_parse got removed when 'packages' were removed, since the dot notation was overlaid on the existing mnemosyne access syntax.) --- lib/syntax_tools/src/erl_prettypr.erl | 12 ++---- lib/syntax_tools/src/erl_syntax.erl | 73 +++++++-------------------------- lib/syntax_tools/src/erl_syntax_lib.erl | 26 +++++------- 3 files changed, 29 insertions(+), 82 deletions(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_prettypr.erl b/lib/syntax_tools/src/erl_prettypr.erl index 877675772f..919df337d0 100644 --- a/lib/syntax_tools/src/erl_prettypr.erl +++ b/lib/syntax_tools/src/erl_prettypr.erl @@ -851,14 +851,10 @@ lay_2(Node, Ctxt) -> floating(text(".")), lay(erl_syntax:record_access_field(Node), set_prec(Ctxt, PrecR))), - D3 = case erl_syntax:record_access_type(Node) of - none -> - D2; - T -> - beside(beside(floating(text("#")), - lay(T, reset_prec(Ctxt))), - D2) - end, + T = erl_syntax:record_access_type(Node), + D3 = beside(beside(floating(text("#")), + lay(T, reset_prec(Ctxt))), + D2), maybe_parentheses(beside(D1, D3), Prec, Ctxt); record_expr -> diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl index de271d7f2f..7ce9f231a1 100644 --- a/lib/syntax_tools/src/erl_syntax.erl +++ b/lib/syntax_tools/src/erl_syntax.erl @@ -254,7 +254,6 @@ receive_expr_action/1, receive_expr_clauses/1, receive_expr_timeout/1, - record_access/2, record_access/3, record_access_argument/1, record_access_field/1, @@ -607,7 +606,6 @@ type(Node) -> {record, _, _, _, _} -> record_expr; {record, _, _, _} -> record_expr; {record_field, _, _, _, _} -> record_access; - {record_field, _, _, _} -> record_access; {record_index, _, _, _} -> record_index_expr; {remote, _, _, _} -> module_qualifier; {rule, _, _, _, _} -> rule; @@ -4310,49 +4308,32 @@ record_index_expr_field(Node) -> %% ===================================================================== -%% @equiv record_access(Argument, none, Field) - --spec record_access(syntaxTree(), syntaxTree()) -> syntaxTree(). - -record_access(Argument, Field) -> - record_access(Argument, none, Field). - - -%% ===================================================================== -%% @doc Creates an abstract record field access expression. If -%% `Type' is not `none', the result represents -%% "Argument#Type.Field". +%% @doc Creates an abstract record field access expression. The result +%% represents "Argument#Type.Field". %% -%% If `Type' is `none', the result represents -%% "Argument.Field". This is a special -%% form only allowed within Mnemosyne queries. -%% -%% @see record_access/2 %% @see record_access_argument/1 %% @see record_access_type/1 %% @see record_access_field/1 %% @see record_expr/3 -record(record_access, {argument :: syntaxTree(), - type :: 'none' | syntaxTree(), + type :: syntaxTree(), field :: syntaxTree()}). %% type(Node) = record_access %% data(Node) = #record_access{argument :: Argument, type :: Type, %% field :: Field} %% -%% Argument = Field = syntaxTree() -%% Type = none | syntaxTree() +%% Argument = Type = Field = syntaxTree() %% %% `erl_parse' representation: %% %% {record_field, Pos, Argument, Type, Field} -%% {record_field, Pos, Argument, Field} %% %% Argument = Field = erl_parse() %% Type = atom() --spec record_access(syntaxTree(), 'none' | syntaxTree(), syntaxTree()) -> +-spec record_access(syntaxTree(), syntaxTree(), syntaxTree()) -> syntaxTree(). record_access(Argument, Type, Field) -> @@ -4365,16 +4346,11 @@ revert_record_access(Node) -> Argument = record_access_argument(Node), Type = record_access_type(Node), Field = record_access_field(Node), - if Type =:= none -> - {record_field, Pos, Argument, Field}; - true -> - case type(Type) of - atom -> - {record_field, Pos, - Argument, concrete(Type), Field}; - _ -> - Node - end + case type(Type) of + atom -> + {record_field, Pos, Argument, concrete(Type), Field}; + _ -> + Node end. @@ -4387,8 +4363,6 @@ revert_record_access(Node) -> record_access_argument(Node) -> case unwrap(Node) of - {record_field, _, Argument, _} -> - Argument; {record_field, _, Argument, _, _} -> Argument; Node1 -> @@ -4397,21 +4371,14 @@ record_access_argument(Node) -> %% ===================================================================== -%% @doc Returns the type subtree of a `record_access' node, -%% if any. If `Node' represents -%% "Argument.Field", `none' -%% is returned, otherwise if `Node' represents -%% "Argument#Type.Field", -%% `Type' is returned. +%% @doc Returns the type subtree of a `record_access' node. %% %% @see record_access/3 --spec record_access_type(syntaxTree()) -> 'none' | syntaxTree(). +-spec record_access_type(syntaxTree()) -> syntaxTree(). record_access_type(Node) -> case unwrap(Node) of - {record_field, _, _, _} -> - none; {record_field, Pos, _, Type, _} -> set_pos(atom(Type), Pos); Node1 -> @@ -4428,8 +4395,6 @@ record_access_type(Node) -> record_access_field(Node) -> case unwrap(Node) of - {record_field, _, _, Field} -> - Field; {record_field, _, _, _, Field} -> Field; Node1 -> @@ -6661,15 +6626,9 @@ subtrees(T) -> receive_expr_action(T)] end; record_access -> - case record_access_type(T) of - none -> - [[record_access_argument(T)], - [record_access_field(T)]]; - R -> - [[record_access_argument(T)], - [R], - [record_access_field(T)]] - end; + [[record_access_argument(T)], + [record_access_type(T)], + [record_access_field(T)]]; record_expr -> case record_expr_argument(T) of none -> @@ -6786,8 +6745,6 @@ make_tree(parentheses, [[E]]) -> parentheses(E); make_tree(prefix_expr, [[F], [A]]) -> prefix_expr(F, A); make_tree(receive_expr, [C]) -> receive_expr(C); make_tree(receive_expr, [C, [E], A]) -> receive_expr(C, E, A); -make_tree(record_access, [[E], [F]]) -> - record_access(E, F); make_tree(record_access, [[E], [T], [F]]) -> record_access(E, T, F); make_tree(record_expr, [[T], F]) -> record_expr(T, F); diff --git a/lib/syntax_tools/src/erl_syntax_lib.erl b/lib/syntax_tools/src/erl_syntax_lib.erl index 2f0488abec..d5f80e15f4 100644 --- a/lib/syntax_tools/src/erl_syntax_lib.erl +++ b/lib/syntax_tools/src/erl_syntax_lib.erl @@ -1669,7 +1669,7 @@ analyze_record_attribute_tuple(Node) -> %%
`record_expr':
%%
`{atom(), [{atom(), Value}]}'
%%
`record_access':
-%%
`{atom(), atom()} | atom()'
+%%
`{atom(), atom()}'
%%
`record_index_expr':
%%
`{atom(), atom()}'
%% @@ -1679,9 +1679,7 @@ analyze_record_attribute_tuple(Node) -> %% listed in the order they appear. (See %% `analyze_record_field/1' for details on the field %% descriptors). For a `record_access' node, -%% `Info' represents the record name and the field name (or -%% if the record name is not included, only the field name; this is -%% allowed only in Mnemosyne-query syntax). For a +%% `Info' represents the record name and the field name. For a %% `record_index_expr' node, `Info' represents the %% record name and the name field name. %% @@ -1713,18 +1711,14 @@ analyze_record_expr(Node) -> F = erl_syntax:record_access_field(Node), case erl_syntax:type(F) of atom -> - case erl_syntax:record_access_type(Node) of - none -> - {record_access, erl_syntax:atom_value(F)}; - A -> - case erl_syntax:type(A) of - atom -> - {record_access, - {erl_syntax:atom_value(A), - erl_syntax:atom_value(F)}}; - _ -> - throw(syntax_error) - end + A = erl_syntax:record_access_type(Node), + case erl_syntax:type(A) of + atom -> + {record_access, + {erl_syntax:atom_value(A), + erl_syntax:atom_value(F)}}; + _ -> + throw(syntax_error) end; _ -> throw(syntax_error) -- cgit v1.2.3 From b61b839fff720fed3afa7c23d7787c60ff95de15 Mon Sep 17 00:00:00 2001 From: Richard Carlsson Date: Mon, 21 Apr 2014 10:10:28 +0200 Subject: Remove support for Mnemosyne rules --- lib/syntax_tools/src/erl_prettypr.erl | 21 +---- lib/syntax_tools/src/erl_syntax.erl | 135 +------------------------------- lib/syntax_tools/src/erl_syntax_lib.erl | 80 ++----------------- lib/syntax_tools/src/erl_tidy.erl | 30 ++----- lib/syntax_tools/src/igor.erl | 23 ------ 5 files changed, 14 insertions(+), 275 deletions(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_prettypr.erl b/lib/syntax_tools/src/erl_prettypr.erl index 919df337d0..81272e62de 100644 --- a/lib/syntax_tools/src/erl_prettypr.erl +++ b/lib/syntax_tools/src/erl_prettypr.erl @@ -50,8 +50,7 @@ | fun((erl_syntax:syntaxTree(), _, _) -> prettypr:document()). -type clause_t() :: 'case_expr' | 'cond_expr' | 'fun_expr' | 'if_expr' | 'receive_expr' | 'try_expr' - | {'function', prettypr:document()} - | {'rule', prettypr:document()}. + | {'function', prettypr:document()}. -record(ctxt, {prec = 0 :: integer(), sub_indent = 2 :: non_neg_integer(), @@ -587,8 +586,6 @@ lay_2(Node, Ctxt) -> make_case_clause(D1, D2, D3, Ctxt); try_expr -> make_case_clause(D1, D2, D3, Ctxt); - {rule, N} -> - make_rule_clause(N, D1, D2, D3, Ctxt); undefined -> %% If a clause is formatted out of context, we %% use a "fun-expression" clause style. @@ -922,15 +919,6 @@ lay_2(Node, Ctxt) -> D2 = lay(erl_syntax:map_field_exact_value(Node), Ctxt1), par([D1, floating(text(":=")), D2], Ctxt1#ctxt.break_indent); - rule -> - %% Comments on the name will be repeated; cf. - %% `function'. - Ctxt1 = reset_prec(Ctxt), - D1 = lay(erl_syntax:rule_name(Node), Ctxt1), - D2 = lay_clauses(erl_syntax:rule_clauses(Node), - {rule, D1}, Ctxt1), - beside(D2, floating(text("."))); - size_qualifier -> Ctxt1 = set_prec(Ctxt, max_prec()), D1 = lay(erl_syntax:size_qualifier_body(Node), Ctxt1), @@ -1069,10 +1057,6 @@ make_fun_clause_head(N, P, Ctxt) -> beside(N, D) end. -make_rule_clause(N, P, G, B, Ctxt) -> - D = make_fun_clause_head(N, P, Ctxt), - append_rule_body(B, append_guard(G, D, Ctxt), Ctxt). - make_case_clause(P, G, B, Ctxt) -> append_clause_body(B, append_guard(G, P, Ctxt), Ctxt). @@ -1088,9 +1072,6 @@ make_if_clause(_P, G, B, Ctxt) -> append_clause_body(B, D, Ctxt) -> append_clause_body(B, D, floating(text(" ->")), Ctxt). -append_rule_body(B, D, Ctxt) -> - append_clause_body(B, D, floating(text(" :-")), Ctxt). - append_clause_body(B, D, S, Ctxt) -> sep([beside(D, S), nest(Ctxt#ctxt.break_indent, B)]). diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl index 7ce9f231a1..3f2a3e05dd 100644 --- a/lib/syntax_tools/src/erl_syntax.erl +++ b/lib/syntax_tools/src/erl_syntax.erl @@ -270,10 +270,6 @@ record_index_expr/2, record_index_expr_field/1, record_index_expr_type/1, - rule/2, - rule_arity/1, - rule_clauses/1, - rule_name/1, size_qualifier/2, size_qualifier_argument/1, size_qualifier_body/1, @@ -471,19 +467,16 @@ %% record_field %% %% record_index_expr -%% rule %% size_qualifier %% string -%% %% text +%% %% try_expr %% tuple %% underscore -%% %% variable +%% %% warning_marker -%% -%% %% %% %% @@ -539,7 +532,6 @@ %% @see record_expr/2 %% @see record_field/2 %% @see record_index_expr/2 -%% @see rule/2 %% @see size_qualifier/2 %% @see string/1 %% @see text/1 @@ -608,7 +600,6 @@ type(Node) -> {record_field, _, _, _, _} -> record_access; {record_index, _, _, _} -> record_index_expr; {remote, _, _, _} -> module_qualifier; - {rule, _, _, _, _} -> rule; {'try', _, _, _, _, _} -> try_expr; {tuple, _, _} -> tuple; _ -> @@ -691,10 +682,9 @@ is_leaf(Node) -> %% `comment' %% `error_marker' %% `eof_marker' -%% `form_list' %% +%% `form_list' %% `function' -%% `rule' %% `warning_marker' %% `text' %% @@ -707,7 +697,6 @@ is_leaf(Node) -> %% @see error_marker/1 %% @see form_list/1 %% @see function/2 -%% @see rule/2 %% @see warning_marker/1 -spec is_form(syntaxTree()) -> boolean(). @@ -720,7 +709,6 @@ is_form(Node) -> eof_marker -> true; error_marker -> true; form_list -> true; - rule -> true; warning_marker -> true; text -> true; _ -> false @@ -3478,7 +3466,6 @@ module_qualifier_body(Node) -> %% @see function_clauses/1 %% @see function_arity/1 %% @see is_form/1 -%% @see rule/2 %% Don't use the name 'function' for this record, to avoid confusion with %% the tuples on the form {function,Name,Arity} used by erl_parse. @@ -4772,117 +4759,6 @@ binary_comp_body(Node) -> end. -%% ===================================================================== -%% @doc Creates an abstract Mnemosyne rule. If `Clauses' is -%% `[C1, ..., Cn]', the results represents -%% "Name C1; ...; Name -%% Cn.". More exactly, if each `Ci' -%% represents "(Pi1, ..., Pim) Gi -> -%% Bi", then the result represents -%% "Name(P11, ..., P1m) G1 :- -%% B1; ...; Name(Pn1, ..., Pnm) -%% Gn :- Bn.". Rules are source code forms. -%% -%% @see rule_name/1 -%% @see rule_clauses/1 -%% @see rule_arity/1 -%% @see is_form/1 -%% @see function/2 - --record(rule, {name :: syntaxTree(), clauses :: [syntaxTree()]}). - -%% type(Node) = rule -%% data(Node) = #rule{name :: Name, clauses :: Clauses} -%% -%% Name = syntaxTree() -%% Clauses = [syntaxTree()] -%% -%% (See `function' for notes on why the arity is not stored.) -%% -%% `erl_parse' representation: -%% -%% {rule, Pos, Name, Arity, Clauses} -%% -%% Name = atom() -%% Arity = integer() -%% Clauses = [Clause] \ [] -%% Clause = {clause, ...} -%% -%% where the number of patterns in each clause should be equal to -%% the integer `Arity'; see `clause' for documentation on -%% `erl_parse' clauses. - --spec rule(syntaxTree(), [syntaxTree()]) -> syntaxTree(). - -rule(Name, Clauses) -> - tree(rule, #rule{name = Name, clauses = Clauses}). - -revert_rule(Node) -> - Name = rule_name(Node), - Clauses = [revert_clause(C) || C <- rule_clauses(Node)], - Pos = get_pos(Node), - case type(Name) of - atom -> - A = rule_arity(Node), - {rule, Pos, concrete(Name), A, Clauses}; - _ -> - Node - end. - - -%% ===================================================================== -%% @doc Returns the name subtree of a `rule' node. -%% -%% @see rule/2 - --spec rule_name(syntaxTree()) -> syntaxTree(). - -rule_name(Node) -> - case unwrap(Node) of - {rule, Pos, Name, _, _} -> - set_pos(atom(Name), Pos); - Node1 -> - (data(Node1))#rule.name - end. - -%% ===================================================================== -%% @doc Returns the list of clause subtrees of a `rule' node. -%% -%% @see rule/2 - --spec rule_clauses(syntaxTree()) -> [syntaxTree()]. - -rule_clauses(Node) -> - case unwrap(Node) of - {rule, _, _, _, Clauses} -> - Clauses; - Node1 -> - (data(Node1))#rule.clauses - end. - -%% ===================================================================== -%% @doc Returns the arity of a `rule' node. The result is the -%% number of parameter patterns in the first clause of the rule; -%% subsequent clauses are ignored. -%% -%% An exception is thrown if `rule_clauses(Node)' returns -%% an empty list, or if the first element of that list is not a syntax -%% tree `C' of type `clause' such that -%% `clause_patterns(C)' is a nonempty list. -%% -%% @see rule/2 -%% @see rule_clauses/1 -%% @see clause/3 -%% @see clause_patterns/1 - --spec rule_arity(syntaxTree()) -> arity(). - -rule_arity(Node) -> - %% Note that this never accesses the arity field of - %% `erl_parse' rule nodes. - length(clause_patterns(hd(rule_clauses(Node)))). - - %% ===================================================================== %% @doc Creates an abstract generator. The result represents %% "Pattern <- Body". @@ -6368,8 +6244,6 @@ revert_root(Node) -> revert_record_expr(Node); record_index_expr -> revert_record_index_expr(Node); - rule -> - revert_rule(Node); string -> revert_string(Node); try_expr -> @@ -6649,8 +6523,6 @@ subtrees(T) -> record_index_expr -> [[record_index_expr_type(T)], [record_index_expr_field(T)]]; - rule -> - [[rule_name(T)], rule_clauses(T)]; size_qualifier -> [[size_qualifier_body(T)], [size_qualifier_argument(T)]]; @@ -6753,7 +6625,6 @@ make_tree(record_field, [[N]]) -> record_field(N); make_tree(record_field, [[N], [E]]) -> record_field(N, E); make_tree(record_index_expr, [[T], [F]]) -> record_index_expr(T, F); -make_tree(rule, [[N], C]) -> rule(N, C); make_tree(size_qualifier, [[N], [A]]) -> size_qualifier(N, A); make_tree(try_expr, [B, C, H, A]) -> try_expr(B, C, H, A); make_tree(tuple, [E]) -> tuple(E). diff --git a/lib/syntax_tools/src/erl_syntax_lib.erl b/lib/syntax_tools/src/erl_syntax_lib.erl index d5f80e15f4..5b5b18d15b 100644 --- a/lib/syntax_tools/src/erl_syntax_lib.erl +++ b/lib/syntax_tools/src/erl_syntax_lib.erl @@ -35,8 +35,7 @@ analyze_function_name/1, analyze_implicit_fun/1, analyze_import_attribute/1, analyze_module_attribute/1, analyze_record_attribute/1, analyze_record_expr/1, - analyze_record_field/1, analyze_rule/1, - analyze_wild_attribute/1, annotate_bindings/1, + analyze_record_field/1, analyze_wild_attribute/1, annotate_bindings/1, annotate_bindings/2, fold/3, fold_subtrees/3, foldl_listlist/3, function_name_expansions/1, is_fail_expr/1, limit/2, limit/3, map/2, map_subtrees/2, mapfold/3, mapfold_subtrees/3, @@ -527,8 +526,6 @@ vann(Tree, Env) -> vann_try_expr(Tree, Env); function -> vann_function(Tree, Env); - rule -> - vann_rule(Tree, Env); fun_expr -> vann_fun_expr(Tree, Env); list_comp -> @@ -569,15 +566,6 @@ vann_function(Tree, Env) -> Bound = [], {ann_bindings(Tree1, Env, Bound, Free), Bound, Free}. -vann_rule(Tree, Env) -> - Cs = erl_syntax:rule_clauses(Tree), - {Cs1, {_, Free}} = vann_clauses(Cs, Env), - N = erl_syntax:rule_name(Tree), - {N1, _, _} = vann(N, Env), - Tree1 = rewrite(Tree, erl_syntax:rule(N1, Cs1)), - Bound = [], - {ann_bindings(Tree1, Env, Bound, Free), Bound, Free}. - vann_fun_expr(Tree, Env) -> Cs = erl_syntax:fun_expr_clauses(Tree), {Cs1, {_, Free}} = vann_clauses(Cs, Env), @@ -946,7 +934,7 @@ is_fail_expr(E) -> %% %% Forms = syntaxTree() | [syntaxTree()] %% Key = attributes | errors | exports | functions | imports -%% | module | records | rules | warnings +%% | module | records | warnings %% %% @doc Analyzes a sequence of "program forms". The given %% `Forms' may be a single syntax tree of type @@ -1047,16 +1035,6 @@ is_fail_expr(E) -> %% that each record name occurs at most once in the list. The %% order of listing is not defined. %% -%%
`{rules, Rules}'
-%%
    -%%
  • `Rules = [{atom(), integer()}]'
  • -%%
-%% `Rules' is a list of the names of the rules that are -%% defined in `Forms' (cf. -%% `analyze_rule/1'). We do not guarantee that each -%% name occurs at most once in the list. The order of listing is -%% not defined.
-%% %%
`{warnings, Warnings}'
%%
    %%
  • `Warnings = [term()]'
  • @@ -1074,12 +1052,11 @@ is_fail_expr(E) -> %% @see analyze_import_attribute/1 %% @see analyze_record_attribute/1 %% @see analyze_function/1 -%% @see analyze_rule/1 %% @see erl_syntax:error_marker_info/1 %% @see erl_syntax:warning_marker_info/1 -type key() :: 'attributes' | 'errors' | 'exports' | 'functions' | 'imports' - | 'module' | 'records' | 'rules' | 'warnings'. + | 'module' | 'records' | 'warnings'. -type info_pair() :: {key(), term()}. -spec analyze_forms(erl_syntax:forms()) -> [info_pair()]. @@ -1099,8 +1076,6 @@ collect_form(Node, Info) -> Info; {function, Name} -> finfo_add_function(Name, Info); - {rule, Name} -> - finfo_add_rule(Name, Info); {error_marker, Data} -> finfo_add_error(Data, Info); {warning_marker, Data} -> @@ -1136,8 +1111,7 @@ collect_attribute(_, {N, V}, Info) -> records = [] :: [{atom(), [{atom(), field_default()}]}], errors = [] :: [term()], warnings = [] :: [term()], - functions = [] :: [{atom(), arity()}], - rules = [] :: [{atom(), arity()}]}). + functions = [] :: [{atom(), arity()}]}). -type field_default() :: 'none' | erl_syntax:syntaxTree(). @@ -1183,9 +1157,6 @@ finfo_add_warning(R, Info) -> finfo_add_function(F, Info) -> Info#forms{functions = [F | Info#forms.functions]}. -finfo_add_rule(F, Info) -> - Info#forms{rules = [F | Info#forms.rules]}. - finfo_to_list(Info) -> [{Key, Value} || {Key, {value, Value}} <- @@ -1197,8 +1168,7 @@ finfo_to_list(Info) -> {records, list_value(Info#forms.records)}, {errors, list_value(Info#forms.errors)}, {warnings, list_value(Info#forms.warnings)}, - {functions, list_value(Info#forms.functions)}, - {rules, list_value(Info#forms.rules)} + {functions, list_value(Info#forms.functions)} ]]. list_value([]) -> @@ -1229,10 +1199,6 @@ list_value(List) -> %% %%
    where `Info = analyze_function(Node)'.
    %% -%%
    `{rule, Info}'
    -%% -%%
    where `Info = analyze_rule(Node)'.
    -%% %%
    `{warning_marker, Info}'
    %% %%
    where `Info = @@ -1245,7 +1211,6 @@ list_value(List) -> %% %% @see analyze_attribute/1 %% @see analyze_function/1 -%% @see analyze_rule/1 %% @see erl_syntax:is_form/1 %% @see erl_syntax:error_marker_info/1 %% @see erl_syntax:warning_marker_info/1 @@ -1258,8 +1223,6 @@ analyze_form(Node) -> {attribute, analyze_attribute(Node)}; function -> {function, analyze_function(Node)}; - rule -> - {rule, analyze_rule(Node)}; error_marker -> {error_marker, erl_syntax:error_marker_info(Node)}; warning_marker -> @@ -1829,8 +1792,6 @@ analyze_file_attribute(Node) -> %% The evaluation throws `syntax_error' if %% `Node' does not represent a well-formed function %% definition. -%% -%% @see analyze_rule/1 -spec analyze_function(erl_syntax:syntaxTree()) -> {atom(), arity()}. @@ -1850,37 +1811,6 @@ analyze_function(Node) -> end. -%% ===================================================================== -%% @spec analyze_rule(Node::syntaxTree()) -> {atom(), integer()} -%% -%% @doc Returns the name and arity of a Mnemosyne rule. The result is a -%% pair `{Name, A}' if `Node' represents a rule -%% "`Name(P_1, ..., P_A) :- ...'". -%% -%% The evaluation throws `syntax_error' if -%% `Node' does not represent a well-formed Mnemosyne -%% rule. -%% -%% @see analyze_function/1 - --spec analyze_rule(erl_syntax:syntaxTree()) -> {atom(), arity()}. - -analyze_rule(Node) -> - case erl_syntax:type(Node) of - rule -> - N = erl_syntax:rule_name(Node), - case erl_syntax:type(N) of - atom -> - {erl_syntax:atom_value(N), - erl_syntax:rule_arity(Node)}; - _ -> - throw(syntax_error) - end; - _ -> - throw(syntax_error) - end. - - %% ===================================================================== %% @spec analyze_implicit_fun(Node::syntaxTree()) -> FunctionName %% diff --git a/lib/syntax_tools/src/erl_tidy.erl b/lib/syntax_tools/src/erl_tidy.erl index 38e0c2099b..db7f0939a3 100644 --- a/lib/syntax_tools/src/erl_tidy.erl +++ b/lib/syntax_tools/src/erl_tidy.erl @@ -792,16 +792,11 @@ keep_form(Form, Used, Opts) -> N = erl_syntax_lib:analyze_function(Form), case sets:is_element(N, Used) of false -> - report_removed_def("function", N, Form, Opts), - false; - true -> - true - end; - rule -> - N = erl_syntax_lib:analyze_rule(Form), - case sets:is_element(N, Used) of - false -> - report_removed_def("rule", N, Form, Opts), + {F, A} = N, + File = proplists:get_value(file, Opts, ""), + report({File, erl_syntax:get_pos(Form), + "removing unused function `~w/~w'."}, + [F, A], Opts), false; true -> true @@ -823,12 +818,6 @@ keep_form(Form, Used, Opts) -> true end. -report_removed_def(Type, {N, A}, Form, Opts) -> - File = proplists:get_value(file, Opts, ""), - report({File, erl_syntax:get_pos(Form), - "removing unused ~s `~w/~w'."}, - [Type, N, A], Opts). - collect_functions(Forms) -> lists:foldl( fun (F, {Names, Defs}) -> @@ -837,10 +826,6 @@ collect_functions(Forms) -> N = erl_syntax_lib:analyze_function(F), {sets:add_element(N, Names), dict:store(N, {F, []}, Defs)}; - rule -> - N = erl_syntax_lib:analyze_rule(F), - {sets:add_element(N, Names), - dict:store(N, {F, []}, Defs)}; _ -> {Names, Defs} end @@ -855,11 +840,6 @@ update_forms([F | Fs], Defs, Imports, Opts) -> {F1, Fs1} = dict:fetch(N, Defs), [F1 | lists:reverse(Fs1)] ++ update_forms(Fs, Defs, Imports, Opts); - rule -> - N = erl_syntax_lib:analyze_rule(F), - {F1, Fs1} = dict:fetch(N, Defs), - [F1 | lists:reverse(Fs1)] ++ update_forms(Fs, Defs, Imports, - Opts); attribute -> [update_attribute(F, Imports, Opts) | update_forms(Fs, Defs, Imports, Opts)]; diff --git a/lib/syntax_tools/src/igor.erl b/lib/syntax_tools/src/igor.erl index 0420508f2a..eac5af5540 100644 --- a/lib/syntax_tools/src/igor.erl +++ b/lib/syntax_tools/src/igor.erl @@ -1713,8 +1713,6 @@ transform(Tree, Env, St) -> transform_function(Tree, Env, St); implicit_fun -> transform_implicit_fun(Tree, Env, St); - rule -> - transform_rule(Tree, Env, St); record_expr -> transform_record(Tree, Env, St); record_index_expr -> @@ -1778,27 +1776,6 @@ renaming_note(Name) -> rename_atom(Node, Atom) -> rewrite(Node, erl_syntax:atom(Atom)). -%% Renaming Mnemosyne rules (just like function definitions) - -transform_rule(T, Env, St) -> - {T1, St1} = default_transform(T, Env, St), - F = erl_syntax_lib:analyze_rule(T1), - {V, Text} = case (Env#code.map)(F) of - F -> - %% Not renamed - {none, []}; - {Atom, _Arity} -> - %% Renamed - Cs = erl_syntax:rule_clauses(T1), - N = rename_atom( - erl_syntax:rule_name(T1), - Atom), - T2 = rewrite(T1, - erl_syntax:rule(N, Cs)), - {{value, T2}, renaming_note(Atom)} - end, - {maybe_modified(V, T1, 2, Text, Env), St1}. - %% Renaming "implicit fun" expressions (done quietly). transform_implicit_fun(T, Env, St) -> -- cgit v1.2.3 From 70f7c1745e8fb461566c57e77e4188052e84a18c Mon Sep 17 00:00:00 2001 From: Richard Carlsson Date: Wed, 8 Aug 2012 16:10:07 +0300 Subject: fix bad format of error in epp_dodger:parse_file/3 --- lib/syntax_tools/src/epp_dodger.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/epp_dodger.erl b/lib/syntax_tools/src/epp_dodger.erl index 7e12eab1b5..3ca7a8197e 100644 --- a/lib/syntax_tools/src/epp_dodger.erl +++ b/lib/syntax_tools/src/epp_dodger.erl @@ -88,7 +88,7 @@ %% This is a so-called Erlang I/O ErrorInfo structure; see the {@link %% //stdlib/io} module for details. --type errorinfo() :: term(). % {integer(), atom(), term()}. +-type errorinfo() :: {integer(), atom(), term()}. -type option() :: atom() | {atom(), term()}. @@ -208,8 +208,8 @@ do_parse_file(DefEncoding, File, Parser, Options) -> try Parser(Dev, 1, Options) after ok = file:close(Dev) end; - {error, _} = Error -> - Error + {error, Error} -> + {error, {0, file, Error}} % defer to file:format_error/1 end. find_invalid_unicode([H|T]) -> -- cgit v1.2.3 From d70818e34708114df1d4d16f5512ce584309a292 Mon Sep 17 00:00:00 2001 From: Hans Bolinder Date: Fri, 31 Oct 2014 09:35:27 +0100 Subject: syntax_tools: Use module erl_anno --- lib/syntax_tools/src/epp_dodger.erl | 2 +- lib/syntax_tools/src/erl_recomment.erl | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/epp_dodger.erl b/lib/syntax_tools/src/epp_dodger.erl index 3ca7a8197e..39c522fd11 100644 --- a/lib/syntax_tools/src/epp_dodger.erl +++ b/lib/syntax_tools/src/epp_dodger.erl @@ -454,7 +454,7 @@ io_error(L, Desc) -> {L, ?MODULE, Desc}. start_pos([T | _Ts], _L) -> - element(2, T); + erl_anno:line(element(2, T)); start_pos([], L) -> L. diff --git a/lib/syntax_tools/src/erl_recomment.erl b/lib/syntax_tools/src/erl_recomment.erl index 7b2f9f7adb..72e1e2d2f5 100644 --- a/lib/syntax_tools/src/erl_recomment.erl +++ b/lib/syntax_tools/src/erl_recomment.erl @@ -123,7 +123,6 @@ recomment_forms(Tree, Cs, Insert) -> form_list -> Tree1 = erl_syntax:flatten_form_list(Tree), Node = build_tree(Tree1), - %% Here we make a small assumption about the substructure of %% a `form_list' tree: it has exactly one group of subtrees. [Node1] = node_subtrees(Node), @@ -753,7 +752,13 @@ get_line(Node) -> {_, L, _} when is_integer(L) -> L; Pos -> - exit({bad_position, Pos}) + try erl_anno:line(Pos) of + Line -> + Line + catch + _:_ -> + exit({bad_position, Pos}) + end end. -- cgit v1.2.3 From e533ea29e80a641f247022f321a3bf1a1456c56e Mon Sep 17 00:00:00 2001 From: Richard Carlsson Date: Tue, 23 Dec 2014 14:23:35 +0100 Subject: Include Merl in Syntax Tools --- lib/syntax_tools/src/Makefile | 21 +- lib/syntax_tools/src/merl.erl | 1241 +++++++++++++++++++++++++++++ lib/syntax_tools/src/merl_tests.erl | 539 +++++++++++++ lib/syntax_tools/src/merl_transform.erl | 262 ++++++ lib/syntax_tools/src/syntax_tools.app.src | 2 + 5 files changed, 2064 insertions(+), 1 deletion(-) create mode 100644 lib/syntax_tools/src/merl.erl create mode 100644 lib/syntax_tools/src/merl_tests.erl create mode 100644 lib/syntax_tools/src/merl_transform.erl (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/Makefile b/lib/syntax_tools/src/Makefile index c9fbad8f9a..2c565cee7f 100644 --- a/lib/syntax_tools/src/Makefile +++ b/lib/syntax_tools/src/Makefile @@ -22,6 +22,9 @@ RELSYSDIR = $(RELEASE_PATH)/lib/syntax_tools-$(VSN) # EBIN = ../ebin +INCLUDE=../include + +ERL_COMPILE_FLAGS += -pa $(EBIN) -pa ./ -I$(INCLUDE) ifeq ($(NATIVE_LIBS_ENABLED),yes) ERL_COMPILE_FLAGS += +native @@ -30,10 +33,15 @@ ERL_COMPILE_FLAGS += +nowarn_shadow_vars +warn_unused_import -Werror # +warn_mis SOURCES=erl_syntax.erl erl_prettypr.erl erl_syntax_lib.erl \ erl_comment_scan.erl erl_recomment.erl erl_tidy.erl \ - epp_dodger.erl prettypr.erl igor.erl + epp_dodger.erl prettypr.erl igor.erl \ + merl.erl merl_transform.erl + +INCLUDE_FILES = merl.hrl OBJECTS=$(SOURCES:%.erl=$(EBIN)/%.$(EMULATOR)) $(APP_TARGET) $(APPUP_TARGET) +INCLUDE_DELIVERABLES = $(INCLUDE_FILES:%=$(INCLUDE)/%) + APP_FILE= syntax_tools.app APP_SRC= $(APP_FILE).src APP_TARGET= $(EBIN)/$(APP_FILE) @@ -52,6 +60,7 @@ all: $(OBJECTS) clean: + rm -f ./merl_transform.beam rm -f $(OBJECTS) rm -f core *~ @@ -64,6 +73,14 @@ realclean: clean $(EBIN)/%.$(EMULATOR):%.erl $(erlc_verbose)erlc -W $(ERL_COMPILE_FLAGS) -o$(EBIN) $< +# special rules and dependencies to apply the transform to itself +$(EBIN)/merl_transform.beam: $(EBIN)/merl.beam ./merl_transform.beam \ + ../include/merl.hrl +./merl_transform.beam: ./merl_transform.erl $(EBIN)/merl.beam \ + ../include/merl.hrl + $(V_ERLC) -DMERL_NO_TRANSFORM $(ERL_COMPILE_FLAGS) -o ./ $< + + # ---------------------------------------------------- # Special Build Targets # ---------------------------------------------------- @@ -84,6 +101,8 @@ release_spec: opt $(INSTALL_DATA) $(OBJECTS) "$(RELSYSDIR)/ebin" $(INSTALL_DIR) "$(RELSYSDIR)/src" $(INSTALL_DATA) $(SOURCES) "$(RELSYSDIR)/src" + $(INSTALL_DIR) "$(RELSYSDIR)/include" + $(INSTALL_DATA) $(INCLUDE_DELIVERABLES) "$(RELSYSDIR)/include" release_docs_spec: diff --git a/lib/syntax_tools/src/merl.erl b/lib/syntax_tools/src/merl.erl new file mode 100644 index 0000000000..1e7ccc525a --- /dev/null +++ b/lib/syntax_tools/src/merl.erl @@ -0,0 +1,1241 @@ +%% --------------------------------------------------------------------- +%% 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 +%% +%% 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. +%% +%% Note: EDoc uses @@ and @} as escape sequences, so in the doc text below, +%% `@@' must be written `@@@@' and `@}' must be written `@@}'. +%% +%% @author Richard Carlsson +%% @copyright 2010-2015 Richard Carlsson +%% +%% @doc Metaprogramming in Erlang. +%% Merl is a more user friendly interface to the `erl_syntax' module, making +%% it easy both to build new ASTs from scratch and to +%% match and decompose existing ASTs. For details that are outside the scope +%% of Merl itself, please see the documentation of {@link erl_syntax}. +%% +%% == Quick start == +%% +%% To enable the full power of Merl, your module needs to include the Merl +%% header file: +%% ```-include_lib("syntax_tools/include/merl.hrl").''' +%% +%% Then, you can use the `?Q(Text)' macros in your code to create ASTs or match +%% on existing ASTs. For example: +%% ```Tuple = ?Q("{foo, 42}"), +%% ?Q("{foo, _@Number}") = Tuple, +%% Call = ?Q("foo:bar(_@Number)")''' +%% +%% Calling `merl:print(Call)' will then print the following code: +%% ```foo:bar(42)''' +%% +%% The `?Q' macros turn the quoted code fragments into ASTs, and lifts +%% metavariables such as `_@Tuple' and `_@Number' to the level of your Erlang +%% code, so you can use the corresponding Erlang variables `Tuple' and `Number' +%% directly. This is the most straightforward way to use Merl, and in many +%% cases it's all you need. +%% +%% You can even write case switches using `?Q' macros as patterns. For example: +%% ```case AST of +%% ?Q("{foo, _@Foo}") -> handle(Foo); +%% ?Q("{bar, _@Bar}") when erl_syntax:is_integer(Bar) -> handle(Bar); +%% _ -> handle_default() +%% end''' +%% +%% These case switches only allow `?Q(...)' or `_' as clause patterns, and the +%% guards may contain any expressions, not just Erlang guard expressions. +%% +%% If the macro `MERL_NO_TRANSFORM' is defined before the `merl.hrl' header +%% file is included, the parse transform used by Merl will be disabled, and in +%% that case, the match expressions `?Q(...) = ...', case switches using +%% `?Q(...)' patterns, and automatic metavariables like `_@Tuple' cannot be +%% used in your code, but the Merl macros and functions still work. To do +%% metavariable substitution, you need to use the `?Q(Text, Map)' macro, e.g.: +%% ```Tuple = ?Q("{foo, _@bar, _@baz}", [{bar, Bar}, {baz,Baz}])''' +%% +%% The text given to a `?Q(Text)' macro can be either a single string, or a +%% list of strings. The latter is useful when you need to split a long +%% expression over multiple lines, e.g.: +%% ```?Q(["case _@Expr of", +%% " {foo, X} -> f(X);", +%% " {bar, X} -> g(X)", +%% " _ -> h(X)" +%% "end"])''' +%% If there is a syntax error somewhere in the text (like the missing semicolon +%% in the second clause above) this allows Merl to generate an error message +%% pointing to the exact line in your source code. (Just remember to +%% comma-separate the strings in the list, otherwise Erlang will concatenate +%% the string fragments as if they were a single string.) +%% +%% == Metavariable syntax == +%% +%% There are several ways to write a metavariable in your quoted code: +%%
      +%%
    • Atoms starting with `@', for example `` '@foo' '' or `` '@Foo' ''
    • +%%
    • Variables starting with `_@', for example `_@bar' or `_@Bar'
    • +%%
    • Strings starting with ``"'@'', for example ``"'@File"''
    • +%%
    • Integers starting with 909, for example `9091' or `909123'
    • +%%
    +%% Following the prefix, one or more `_' or `0' characters may be used to +%% indicate "lifting" of the variable one or more levels, and after that, a `@' +%% or `9' character indicates a glob metavariable (matching zero or more +%% elements in a sequence) rather than a normal metavariable. For example: +%%
      +%%
    • `` '@_foo' '' is lifted one level, and `_@__foo' is lifted two +%% levels
    • +%%
    • `_@@@@bar' is a glob variable, and `_@_@bar' is a lifted glob +%% variable
    • +%%
    • `90901' is a lifted variable,`90991' is a glob variable, and `9090091' +%% is a glob variable lifted two levels
    • +%%
    +%% (Note that the last character in the name is never considered to be a lift +%% or glob marker, hence, `_@__' and `90900' are only lifted one level, not +%% two. Also note that globs only matter for matching; when doing +%% substitutions, a non-glob variable can be used to inject a sequence of +%% elements, and vice versa.) +%% +%% If the name after the prefix and any lift and glob markers is `_' or `0', +%% the variable is treated as an anonymous catch-all pattern in matches. For +%% example, `_@_', `_@@@@_', `_@__', or even `_@__@_'. +%% +%% Finally, if the name without any prefixes or lift/glob markers begins with +%% an uppercase character, as in `_@Foo' or `_@_@Foo', it will become a +%% variable on the Erlang level, and can be used to easily deconstruct and +%% construct syntax trees: +%% ```case Input of +%% ?Q("{foo, _@Number}") -> ?Q("foo:bar(_@Number)"); +%% ...''' +%% We refer to these as "automatic metavariables". If in addition the name ends +%% with `@', as in `_@Foo@', the value of the variable as an Erlang term will +%% be automatically converted to the corresponding abstract syntax tree when +%% used to construct a larger tree. For example, in: +%% ```Bar = {bar, 42}, +%% Foo = ?Q("{foo, _@Bar@@}")''' +%% (where Bar is just some term, not a syntax tree) the result `Foo' will be a +%% syntax tree representing `{foo, {bar, 42}}'. This avoids the need for +%% temporary variables in order to inject data, as in +%% ```TmpBar = erl_syntax:abstract(Bar), +%% Foo = ?Q("{foo, _@TmpBar}")''' +%% +%% If the context requires an integer rather than a variable, an atom, or a +%% string, you cannot use the uppercase convention to mark an automatic +%% metavariable. Instead, if the integer (without the `909'-prefix and +%% lift/glob markers) ends in a `9', the integer will become an Erlang-level +%% variable prefixed with `Q', and if it ends with `99' it will also be +%% automatically abstracted. For example, the following will increment the +%% arity of the exported function f: +%% ```case Form of +%% ?Q("-export([f/90919]).") -> +%% Q2 = erl_syntax:concrete(Q1) + 1, +%% ?Q("-export([f/909299])."); +%% ...''' +%% +%% == When to use the various forms of metavariables == +%% +%% Merl can only parse a fragment of text if it follows the basic syntactical +%% rules of Erlang. In most places, a normal Erlang variable can be used as +%% metavariable, for example: +%% ```?Q("f(_@Arg)") = Expr''' +%% but if you want to match on something like the name of a function, you have +%% to use an atom as metavariable: +%% ```?Q("'@Name'() -> _@@@@_." = Function''' +%% (note the anonymous glob variable `_@@@@_' to ignore the function body). +%% +%% In some contexts, only a string or an integer is allowed. For example, the +%% directive `-file(Name, Line)' requires that `Name' is a string literal and +%% `Line' an integer literal: +%% +%% ```?Q("-file(\"'@File\", 9090).") = ?Q("-file(\"foo.erl\", 42).")).''' +%% This will extract the string literal `"foo.erl"' into the variable `Foo'. +%% Note the use of the anonymous variable `9090' to ignore the line number. To +%% match and also bind a metavariable that must be an integer literal, we can +%% use the convention of ending the integer with a 9, turning it into a +%% Q-prefixed variable on the Erlang level (see the previous section). +%% +%% === Globs === +%% +%% Whenever you want to match out a number of elements in a sequence (zero or +%% more) rather than a fixed set of elements, you need to use a glob. For +%% example: +%% ```?Q("{_@@@@Elements}") = ?Q({a, b, c})''' +%% will bind Elements to the list of individual syntax trees representing the +%% atoms `a', `b', and `c'. This can also be used with static prefix and suffix +%% elements in the sequence. For example: +%% ```?Q("{a, b, _@@@@Elements}") = ?Q({a, b, c, d})''' +%% will bind Elements to the list of the `c' and `d' subtrees, and +%% ```?Q("{_@@@@Elements, c, d}") = ?Q({a, b, c, d})''' +%% will bind Elements to the list of the `a' and `b' subtrees. You can even use +%% plain metavariables in the prefix or suffix: +%% ```?Q("{_@First, _@@@@Rest}") = ?Q({a, b, c})''' +%% or +%% ```?Q("{_@@@@_, _@Last}") = ?Q({a, b, c})''' +%% (ignoring all but the last element). You cannot however have two globs as +%% part of the same sequence. +%% +%% === Lifted metavariables === +%% +%% In some cases, the Erlang syntax rules make it impossible to place a +%% metavariable directly where you would like it. For example, you cannot +%% write: +%% ```?Q("-export([_@@@@Name]).")''' +%% to match out all name/arity pairs in the export list, or to insert a list of +%% exports in a declaration, because the Erlang parser only allows elements on +%% the form `A/I' (where `A' is an atom and `I' an integer) in the export list. +%% A variable like the above is not allowed, but neither is a single atom or +%% integer, so `` '@@@@Name' '' or `909919' wouldn't work either. +%% +%% What you have to do in such cases is to write your metavariable in a +%% syntactically valid position, and use lifting markers to denote where it +%% should really apply, as in: +%% ```?Q("-export(['@@_@@Name'/0]).")''' +%% This causes the variable to be lifted (after parsing) to the next higher +%% level in the syntax tree, replacing that entire subtree. In this case, the +%% `` '@@_@@Name'/0 '' will be replaced with `` '@@@@Name' '', and the ``/0'' +%% part was just used as dummy notation and will be discarded. +%% +%% You may even need to apply lifting more than once. To match the entire +%% export list as a single syntax tree, you can write: +%% ```?Q("-export(['@@__Name'/0]).")''' +%% using two underscores, but with no glob marker this time. This will make the +%% entire ``['@@__Name'/0]'' part be replaced with `` '@@Name' ''. +%% +%% Sometimes, the tree structure of a code fragment isn't very obvious, and +%% parts of the structure may be invisible when printed as source code. For +%% instance, a simple function definition like the following: +%% ```zero() -> 0.''' +%% consists of the name (the atom `zero'), and a list of clauses containing the +%% single clause `() -> 0'. The clause consists of an argument list (empty), a +%% guard (empty), and a body (which is always a list of expressions) containing +%% the single expression `0'. This means that to match out the name and the +%% list of clauses of any function, you'll need to use a pattern like +%% ``?Q("'@Name'() -> _@_@Body.")'', using a dummy clause whose body is a glob +%% lifted one level. +%% +%% To visualize the structure of a syntax tree, you can use the function +%% `merl:show(T)', which prints a summary. For example, entering +%% ```merl:show(merl:quote("inc(X, Y) when Y > 0 -> X + Y."))''' +%% in the Erlang shell will print the following (where the `+' signs separate +%% groups of subtrees on the same level): +%% ```function: inc(X, Y) when ... -> X + Y. +%% atom: inc +%% + +%% clause: (X, Y) when ... -> X + Y +%% variable: X +%% variable: Y +%% + +%% disjunction: Y > 0 +%% conjunction: Y > 0 +%% infix_expr: Y > 0 +%% variable: Y +%% + +%% operator: > +%% + +%% integer: 0 +%% + +%% infix_expr: X + Y +%% variable: X +%% + +%% operator: + +%% + +%% variable: Y''' +%% +%% This shows another important non-obvious case: a clause guard, even if it's +%% as simple as `Y > 0', always consists of a single disjunction of one or more +%% conjunctions of tests, much like a tuple of tuples. Thus: +%%
      +%%
    • ``"when _@Guard ->"'' will only match a guard with exactly one +%% test
    • +%%
    • ``"when _@@@@Guard ->"'' will match a guard with one or more +%% comma-separated tests (but no semicolons), binding `Guard' to the list +%% of tests
    • +%%
    • ``"when _@_Guard ->"'' will match just like the previous pattern, but +%% binds `Guard' to the conjunction subtree
    • +%%
    • ``"when _@_@Guard ->"'' will match an arbitrary nonempty guard, +%% binding `Guard' to the list of conjunction subtrees
    • +%%
    • ``"when _@__Guard ->"'' will match like the previous pattern, but +%% binds `Guard' to the whole disjunction subtree
    • +%%
    • and finally, ``"when _@__@Guard ->"'' will match any clause, +%% binding `Guard' to `[]' if the guard is empty and to `[Disjunction]' +%% otherwise
    • +%%
    +%% +%% Thus, the following pattern matches all possible clauses: +%% ```"(_@@Args) when _@__@Guard -> _@@Body"''' +%% @end + +-module(merl). + +-export([term/1, var/1, print/1, show/1]). + +-export([quote/1, quote/2, qquote/2, qquote/3]). + +-export([template/1, tree/1, subst/2, tsubst/2, alpha/2, match/2, switch/2]). + +-export([template_vars/1, meta_template/1]). + +-export([compile/1, compile/2, compile_and_load/1, compile_and_load/2]). + +%% NOTE: this module may not include merl.hrl! + +-type tree() :: erl_syntax:syntaxTree(). + +-type tree_or_trees() :: tree() | [tree()]. + +-type pattern() :: tree() | template(). + +-type pattern_or_patterns() :: pattern() | [pattern()]. + +-type env() :: [{Key::id(), pattern_or_patterns()}]. + +-type id() :: atom() | integer(). + +%% A list of strings or binaries is assumed to represent individual lines, +%% while a flat string or binary represents source code containing newlines. +-type text() :: string() | binary() | [string()] | [binary()]. + +-type location() :: erl_scan:location(). + + +%% ------------------------------------------------------------------------ +%% Compiling and loading code directly to memory + +%% @equiv compile(Code, []) +compile(Code) -> + compile(Code, []). + +%% @doc Compile a syntax tree or list of syntax trees representing a module +%% into a binary BEAM object. +%% @see compile_and_load/2 +%% @see compile/1 +compile(Code, Options) when not is_list(Code)-> + case type(Code) of + form_list -> compile(erl_syntax:form_list_elements(Code)); + _ -> compile([Code], Options) + end; +compile(Code, Options0) when is_list(Options0) -> + Forms = [erl_syntax:revert(F) || F <- Code], + Options = [verbose, report_errors, report_warnings, binary | Options0], + compile:noenv_forms(Forms, Options). + + +%% @equiv compile_and_load(Code, []) +compile_and_load(Code) -> + compile_and_load(Code, []). + +%% @doc Compile a syntax tree or list of syntax trees representing a module +%% and load the resulting module into memory. +%% @see compile/2 +%% @see compile_and_load/1 +compile_and_load(Code, Options) -> + case compile(Code, Options) of + {ok, ModuleName, Binary} -> + _ = code:load_binary(ModuleName, "", Binary), + {ok, Binary}; + Other -> Other + end. + + +%% ------------------------------------------------------------------------ +%% Utility functions + + +-spec var(atom()) -> tree(). + +%% @doc Create a variable. + +var(Name) -> + erl_syntax:variable(Name). + + +-spec term(term()) -> tree(). + +%% @doc Create a syntax tree for a constant term. + +term(Term) -> + erl_syntax:abstract(Term). + + +%% @doc Pretty-print a syntax tree or template to the standard output. This +%% is a utility function for development and debugging. + +print(Ts) when is_list(Ts) -> + lists:foreach(fun print/1, Ts); +print(T) -> + io:put_chars(erl_prettypr:format(tree(T))), + io:nl(). + +%% @doc Print the structure of a syntax tree or template to the standard +%% output. This is a utility function for development and debugging. + +show(Ts) when is_list(Ts) -> + lists:foreach(fun show/1, Ts); +show(T) -> + io:put_chars(pp(tree(T), 0)), + io:nl(). + +pp(T, I) -> + [lists:duplicate(I, $\s), + limit(lists:flatten([atom_to_list(type(T)), ": ", + erl_prettypr:format(erl_syntax_lib:limit(T,3))]), + 79-I), + $\n, + pp_1(lists:filter(fun (X) -> X =/= [] end, subtrees(T)), I+2) + ]. + +pp_1([G], I) -> + pp_2(G, I); +pp_1([G | Gs], I) -> + [pp_2(G, I), lists:duplicate(I, $\s), "+\n" | pp_1(Gs, I)]; +pp_1([], _I) -> + []. + +pp_2(G, I) -> + [pp(E, I) || E <- G]. + +%% limit string to N characters, stay on a single line and compact whitespace +limit([$\n | Cs], N) -> limit([$\s | Cs], N); +limit([$\r | Cs], N) -> limit([$\s | Cs], N); +limit([$\v | Cs], N) -> limit([$\s | Cs], N); +limit([$\t | Cs], N) -> limit([$\s | Cs], N); +limit([$\s, $\s | Cs], N) -> limit([$\s | Cs], N); +limit([C | Cs], N) when C < 32 -> limit(Cs, N); +limit([C | Cs], N) when N > 3 -> [C | limit(Cs, N-1)]; +limit([_C1, _C2, _C3, _C4 | _Cs], 3) -> "..."; +limit(Cs, 3) -> Cs; +limit([_C1, _C2, _C3 | _], 2) -> ".."; +limit(Cs, 2) -> Cs; +limit([_C1, _C2 | _], 1) -> "."; +limit(Cs, 1) -> Cs; +limit(_, _) -> []. + +%% ------------------------------------------------------------------------ +%% Parsing and instantiating code fragments + + +-spec qquote(Text::text(), Env::env()) -> tree_or_trees(). + +%% @doc Parse text and substitute meta-variables. +%% +%% @equiv qquote(1, Text, Env) + +qquote(Text, Env) -> + qquote(1, Text, Env). + + +-spec qquote(StartPos::location(), Text::text(), Env::env()) -> tree_or_trees(). + +%% @doc Parse text and substitute meta-variables. Takes an initial scanner +%% starting position as first argument. +%% +%% The macro `?Q(Text, Env)' expands to `merl:qquote(?LINE, Text, Env)'. +%% +%% @see quote/2 + +qquote(StartPos, Text, Env) -> + subst(quote(StartPos, Text), Env). + + +-spec quote(Text::text()) -> tree_or_trees(). + +%% @doc Parse text. +%% +%% @equiv quote(1, Text) + +quote(Text) -> + quote(1, Text). + + +-spec quote(StartPos::location(), Text::text()) -> tree_or_trees(). + +%% @doc Parse text. Takes an initial scanner starting position as first +%% argument. +%% +%% The macro `?Q(Text)' expands to `merl:quote(?LINE, Text, Env)'. +%% +%% @see quote/1 + +quote({Line, Col}, Text) + when is_integer(Line), is_integer(Col) -> + quote_1(Line, Col, Text); +quote(StartPos, Text) when is_integer(StartPos) -> + quote_1(StartPos, undefined, Text). + +quote_1(StartLine, StartCol, Text) -> + %% be backwards compatible as far as R12, ignoring any starting column + StartPos = case erlang:system_info(version) of + "5.6" ++ _ -> StartLine; + "5.7" ++ _ -> StartLine; + "5.8" ++ _ -> StartLine; + _ when StartCol =:= undefined -> StartLine; + _ -> {StartLine, StartCol} + end, + FlatText = flatten_text(Text), + {ok, Ts, _} = erl_scan:string(FlatText, StartPos), + merge_comments(StartLine, erl_comment_scan:string(FlatText), parse_1(Ts)). + +parse_1(Ts) -> + %% if dot tokens are present, it is assumed that the text represents + %% complete forms, not dot-terminated expressions or similar + case split_forms(Ts) of + {ok, Fs} -> parse_forms(Fs); + error -> + parse_2(Ts) + end. + +split_forms(Ts) -> + split_forms(Ts, [], []). + +split_forms([{dot,_}=T|Ts], Fs, As) -> + split_forms(Ts, [lists:reverse(As, [T]) | Fs], []); +split_forms([T|Ts], Fs, As) -> + split_forms(Ts, Fs, [T|As]); +split_forms([], Fs, []) -> + {ok, lists:reverse(Fs)}; +split_forms([], [], _) -> + error; % no dot tokens found - not representing form(s) +split_forms([], _, [T|_]) -> + fail("incomplete form after ~p", [T]). + +parse_forms([Ts | Tss]) -> + case erl_parse:parse_form(Ts) of + {ok, Form} -> [Form | parse_forms(Tss)]; + {error, R} -> parse_error(R) + end; +parse_forms([]) -> + []. + +parse_2(Ts) -> + %% one or more comma-separated expressions? + %% (recall that Ts has no dot tokens if we get to this stage) + case erl_parse:parse_exprs(Ts ++ [{dot,0}]) of + {ok, Exprs} -> Exprs; + {error, E} -> + parse_3(Ts ++ [{'end',0}, {dot,0}], [E]) + end. + +parse_3(Ts, Es) -> + %% try-clause or clauses? + case erl_parse:parse_exprs([{'try',0}, {atom,0,true}, {'catch',0} | Ts]) of + {ok, [{'try',_,_,_,_,_}=X]} -> + %% get the right kind of qualifiers in the clause patterns + erl_syntax:try_expr_handlers(X); + {error, E} -> + parse_4(Ts, [E|Es]) + end. + +parse_4(Ts, Es) -> + %% fun-clause or clauses? (`(a)' is also a pattern, but `(a,b)' isn't, + %% so fun-clauses must be tried before normal case-clauses + case erl_parse:parse_exprs([{'fun',0} | Ts]) of + {ok, [{'fun',_,{clauses,Cs}}]} -> Cs; + {error, E} -> + parse_5(Ts, [E|Es]) + end. + +parse_5(Ts, Es) -> + %% case-clause or clauses? + case erl_parse:parse_exprs([{'case',0}, {atom,0,true}, {'of',0} | Ts]) of + {ok, [{'case',_,_,Cs}]} -> Cs; + {error, E} -> + %% select the best error to report + parse_error(lists:last(lists:sort([E|Es]))) + end. + +-dialyzer({nowarn_function, parse_error/1}). % no local return + +parse_error({L, M, R}) when is_atom(M), is_integer(L) -> + fail("~w: ~s", [L, M:format_error(R)]); +parse_error({{L,C}, M, R}) when is_atom(M), is_integer(L), is_integer(C) -> + fail("~w:~w: ~s", [L,C,M:format_error(R)]); +parse_error({_, M, R}) when is_atom(M) -> + fail(M:format_error(R)); +parse_error(R) -> + fail("unknown parse error: ~p", [R]). + +%% ------------------------------------------------------------------------ +%% Templates, substitution and matching + +%% Leaves are normal syntax trees, and inner nodes are tuples +%% {template,Type,Attrs,Groups} where Groups are lists of lists of nodes. +%% Metavariables are 1-tuples {VarName}, where VarName is an atom or an +%% integer. {'_'} and {0} work as anonymous variables in matching. Glob +%% metavariables are tuples {'*',VarName}, and {'*','_'} and {'*',0} are +%% anonymous globs. + +%% Note that although template() :: tree() | ..., it is implied that these +%% syntax trees are free from metavariables, so pattern() :: tree() | +%% template() is in fact a wider type than template(). + +-type template() :: tree() + | {id()} + | {'*',id()} + | {template, atom(), term(), [[template()]]}. + +-type template_or_templates() :: template() | [template()]. + +-spec template(pattern_or_patterns()) -> template_or_templates(). + +%% @doc Turn a syntax tree or list of trees into a template or templates. +%% Templates can be instantiated or matched against, and reverted back to +%% normal syntax trees using {@link tree/1}. If the input is already a +%% template, it is not modified further. +%% +%% @see subst/2 +%% @see match/2 +%% @see tree/1 + +template(Trees) when is_list(Trees) -> + [template_0(T) || T <- Trees]; +template(Tree) -> + template_0(Tree). + +template_0({template, _, _, _}=Template) -> Template; +template_0({'*',_}=Template) -> Template; +template_0({_}=Template) -> Template; +template_0(Tree) -> + case template_1(Tree) of + false -> Tree; + {Name} when is_list(Name) -> + fail("bad metavariable: '~s'", [tl(Name)]); % drop v/n from name + Template -> Template + end. + +%% returns either a template or a lifted metavariable {String}, or 'false' +%% if Tree contained no metavariables +template_1(Tree) -> + case subtrees(Tree) of + [] -> + case metavar(Tree) of + {"v_"++Cs}=V when Cs =/= [] -> V; % to be lifted + {"n0"++Cs}=V when Cs =/= [] -> V; % to be lifted + {"v@"++Cs} when Cs =/= [] -> {'*',list_to_atom(Cs)}; + {"n9"++Cs} when Cs =/= [] -> {'*',list_to_integer(Cs)}; + {"v"++Cs} -> {list_to_atom(Cs)}; + {"n"++Cs} -> {list_to_integer(Cs)}; + false -> false + end; + Gs -> + case template_2(Gs, [], false) of + Gs1 when is_list(Gs1) -> + {template, type(Tree), erl_syntax:get_attrs(Tree), Gs1}; + Other -> + Other + end + end. + +template_2([G | Gs], As, Bool) -> + case template_3(G, [], false) of + {"v_"++Cs}=V when Cs =/= [] -> V; % lift further + {"n0"++Cs}=V when Cs =/= [] -> V; % lift further + {"v@"++Cs} when Cs =/= [] -> {'*',list_to_atom(Cs)}; % stop + {"n9"++Cs} when Cs =/= [] -> {'*',list_to_integer(Cs)}; % stop + {"v"++Cs} when is_list(Cs) -> {list_to_atom(Cs)}; % stop + {"n"++Cs} when is_list(Cs) -> {list_to_integer(Cs)}; % stop + false -> template_2(Gs, [G | As], Bool); + G1 -> template_2(Gs, [G1 | As], true) + end; +template_2([], _As, false) -> false; +template_2([], As, true) -> lists:reverse(As). + +template_3([T | Ts], As, Bool) -> + case template_1(T) of + {"v_"++Cs} when Cs =/= [] -> {"v"++Cs}; % lift + {"n0"++Cs} when Cs =/= [] -> {"n"++Cs}; % lift + false -> template_3(Ts, [T | As], Bool); + T1 -> template_3(Ts, [T1 | As], true) + end; +template_3([], _As, false) -> false; +template_3([], As, true) -> lists:reverse(As). + + +%% @doc Turn a template into a syntax tree representing the template. +%% Meta-variables in the template are turned into normal Erlang variables if +%% their names (after the metavariable prefix characters) begin with an +%% uppercase character. E.g., `_@Foo' in the template becomes the variable +%% `Foo' in the meta-template. Furthermore, variables ending with `@' are +%% automatically wrapped in a call to merl:term/1, so e.g. `_@Foo@ in the +%% template becomes `merl:term(Foo)' in the meta-template. + +-spec meta_template(template_or_templates()) -> tree_or_trees(). + +meta_template(Templates) when is_list(Templates) -> + [meta_template_1(T) || T <- Templates]; +meta_template(Template) -> + meta_template_1(Template). + +meta_template_1({template, Type, Attrs, Groups}) -> + erl_syntax:tuple( + [erl_syntax:atom(template), + erl_syntax:atom(Type), + erl_syntax:abstract(Attrs), + erl_syntax:list([erl_syntax:list([meta_template_1(T) || T <- G]) + || G <- Groups])]); +meta_template_1({Var}=V) -> + meta_template_2(Var, V); +meta_template_1({'*',Var}=V) -> + meta_template_2(Var, V); +meta_template_1(Leaf) -> + erl_syntax:abstract(Leaf). + +meta_template_2(Var, V) when is_atom(Var) -> + case atom_to_list(Var) of + [C|_]=Name when C >= $A, C =< $Z ; C >= $À, C =< $Þ, C /= $× -> + case lists:reverse(Name) of + "@"++([_|_]=RevRealName) -> % don't allow empty RealName + RealName = lists:reverse(RevRealName), + erl_syntax:application(erl_syntax:atom(merl), + erl_syntax:atom(term), + [erl_syntax:variable(RealName)]); + _ -> + %% plain automatic metavariable + erl_syntax:variable(Name) + end; + _ -> + erl_syntax:abstract(V) + end; +meta_template_2(Var, V) when is_integer(Var) -> + if Var > 9, (Var rem 10) =:= 9 -> + %% at least 2 digits, ends in 9: make it a Q-variable + if Var > 99, (Var rem 100) =:= 99 -> + %% at least 3 digits, ends in 99: wrap in merl:term/1 + Name = "Q" ++ integer_to_list(Var div 100), + erl_syntax:application(erl_syntax:atom(merl), + erl_syntax:atom(term), + [erl_syntax:variable(Name)]); + true -> + %% plain automatic Q-variable + Name = integer_to_list(Var div 10), + erl_syntax:variable("Q" ++ Name) + end; + true -> + erl_syntax:abstract(V) + end. + + + +-spec template_vars(template_or_templates()) -> [id()]. + +%% @doc Return an ordered list of the metavariables in the template. + +template_vars(Template) -> + template_vars(Template, []). + +template_vars(Templates, Vars) when is_list(Templates) -> + lists:foldl(fun template_vars_1/2, Vars, Templates); +template_vars(Template, Vars) -> + template_vars_1(Template, Vars). + +template_vars_1({template, _, _, Groups}, Vars) -> + lists:foldl(fun (G, V) -> lists:foldl(fun template_vars_1/2, V, G) end, + Vars, Groups); +template_vars_1({Var}, Vars) -> + ordsets:add_element(Var, Vars); +template_vars_1({'*',Var}, Vars) -> + ordsets:add_element(Var, Vars); +template_vars_1(_, Vars) -> + Vars. + + +-spec tree(template_or_templates()) -> tree_or_trees(). + +%% @doc Revert a template to a normal syntax tree. Any remaining +%% metavariables are turned into `@'-prefixed atoms or `909'-prefixed +%% integers. +%% @see template/1 + +tree(Templates) when is_list(Templates) -> + [tree_1(T) || T <- Templates]; +tree(Template) -> + tree_1(Template). + +tree_1({template, Type, Attrs, Groups}) -> + %% flattening here is needed for templates created via source transforms + Gs = [lists:flatten([tree_1(T) || T <- G]) || G <- Groups], + erl_syntax:set_attrs(make_tree(Type, Gs), Attrs); +tree_1({Var}) when is_atom(Var) -> + erl_syntax:atom(list_to_atom("@"++atom_to_list(Var))); +tree_1({Var}) when is_integer(Var) -> + erl_syntax:integer(list_to_integer("909"++integer_to_list(Var))); +tree_1({'*',Var}) when is_atom(Var) -> + erl_syntax:atom(list_to_atom("@@"++atom_to_list(Var))); +tree_1({'*',Var}) when is_integer(Var) -> + erl_syntax:integer(list_to_integer("9099"++integer_to_list(Var))); +tree_1(Leaf) -> + Leaf. % any syntax tree, not necessarily atomic (due to substitutions) + + +-spec subst(pattern_or_patterns(), env()) -> tree_or_trees(). + +%% @doc Substitute metavariables in a pattern or list of patterns, yielding +%% a syntax tree or list of trees as result. Both for normal metavariables +%% and glob metavariables, the substituted value may be a single element or +%% a list of elements. For example, if a list representing `1, 2, 3' is +%% substituted for `var' in either of `[foo, _@var, bar]' or `[foo, _@@var, +%% bar]', the result represents `[foo, 1, 2, 3, bar]'. + +subst(Trees, Env) when is_list(Trees) -> + [subst_0(T, Env) || T <- Trees]; +subst(Tree, Env) -> + subst_0(Tree, Env). + +subst_0(Tree, Env) -> + tree_1(subst_1(template(Tree), Env)). + + +-spec tsubst(pattern_or_patterns(), env()) -> template_or_templates(). + +%% @doc Like subst/2, but does not convert the result from a template back +%% to a tree. Useful if you want to do multiple separate substitutions. +%% @see subst/2 +%% @see tree/1 + +tsubst(Trees, Env) when is_list(Trees) -> + [subst_1(template(T), Env) || T <- Trees]; +tsubst(Tree, Env) -> + subst_1(template(Tree), Env). + +subst_1({template, Type, Attrs, Groups}, Env) -> + Gs1 = [lists:flatten([subst_1(T, Env) || T <- G]) || G <- Groups], + {template, Type, Attrs, Gs1}; +subst_1({Var}=V, Env) -> + case lists:keyfind(Var, 1, Env) of + {Var, TreeOrTrees} -> TreeOrTrees; + false -> V + end; +subst_1({'*',Var}=V, Env) -> + case lists:keyfind(Var, 1, Env) of + {Var, TreeOrTrees} -> TreeOrTrees; + false -> V + end; +subst_1(Leaf, _Env) -> + Leaf. + + +-spec alpha(pattern_or_patterns(), [{id(), id()}]) -> template_or_templates(). + +%% @doc Alpha converts a pattern (renames variables). Similar to tsubst/1, +%% but only renames variables (including globs). +%% @see tsubst/2 + +alpha(Trees, Env) when is_list(Trees) -> + [alpha_1(template(T), Env) || T <- Trees]; +alpha(Tree, Env) -> + alpha_1(template(Tree), Env). + +alpha_1({template, Type, Attrs, Groups}, Env) -> + Gs1 = [lists:flatten([alpha_1(T, Env) || T <- G]) || G <- Groups], + {template, Type, Attrs, Gs1}; +alpha_1({Var}=V, Env) -> + case lists:keyfind(Var, 1, Env) of + {Var, NewVar} -> {NewVar}; + false -> V + end; +alpha_1({'*',Var}=V, Env) -> + case lists:keyfind(Var, 1, Env) of + {Var, NewVar} -> {'*',NewVar}; + false -> V + end; +alpha_1(Leaf, _Env) -> + Leaf. + + +-spec match(pattern_or_patterns(), tree_or_trees()) -> + {ok, env()} | error. + +%% @doc Match a pattern against a syntax tree (or patterns against syntax +%% trees) returning an environment mapping variable names to subtrees; the +%% environment is always sorted on keys. Note that multiple occurrences of +%% metavariables in the pattern is not allowed, but is not checked. +%% +%% @see template/1 +%% @see switch/2 + +match(Patterns, Trees) when is_list(Patterns), is_list(Trees) -> + try {ok, match_1(Patterns, Trees, [])} + catch + error -> error + end; +match(Patterns, Tree) when is_list(Patterns) -> match(Patterns, [Tree]); +match(Pattern, Trees) when is_list(Trees) -> match([Pattern], Trees); +match(Pattern, Tree) -> + try {ok, match_template(template(Pattern), Tree, [])} + catch + error -> error + end. + +match_1([P|Ps], [T | Ts], Dict) -> + match_1(Ps, Ts, match_template(template(P), T, Dict)); +match_1([], [], Dict) -> + Dict; +match_1(_, _, _Dict) -> + erlang:error(merl_match_arity). + +%% match a template against a syntax tree +match_template({template, Type, _, Gs}, Tree, Dict) -> + case type(Tree) of + Type -> match_template_1(Gs, subtrees(Tree), Dict); + _ -> throw(error) % type mismatch + end; +match_template({Var}, _Tree, Dict) + when Var =:= '_' ; Var =:= 0 -> + Dict; % anonymous variable +match_template({Var}, Tree, Dict) -> + orddict:store(Var, Tree, Dict); +match_template(Tree1, Tree2, Dict) -> + %% if Tree1 is not a template, Tree1 and Tree2 are both syntax trees + case compare_trees(Tree1, Tree2) of + true -> Dict; + false -> throw(error) % different trees + end. + +match_template_1([G1 | Gs1], [G2 | Gs2], Dict) -> + match_template_2(G1, G2, match_template_1(Gs1, Gs2, Dict)); +match_template_1([], [], Dict) -> + Dict; +match_template_1(_, _, _Dict) -> + throw(error). % shape mismatch + +match_template_2([{Var} | Ts1], [_ | Ts2], Dict) + when Var =:= '_' ; Var =:= 0 -> + match_template_2(Ts1, Ts2, Dict); % anonymous variable +match_template_2([{Var} | Ts1], [Tree | Ts2], Dict) -> + match_template_2(Ts1, Ts2, orddict:store(Var, Tree, Dict)); +match_template_2([{'*',Var} | Ts1], Ts2, Dict) -> + match_glob(lists:reverse(Ts1), lists:reverse(Ts2), Var, Dict); +match_template_2([T1 | Ts1], [T2 | Ts2], Dict) -> + match_template_2(Ts1, Ts2, match_template(T1, T2, Dict)); +match_template_2([], [], Dict) -> + Dict; +match_template_2(_, _, _Dict) -> + throw(error). % shape mismatch + +%% match the tails in reverse order; no further globs allowed +match_glob([{'*',Var} | _], _, _, _) -> + fail("multiple glob variables in same match group: ~w", [Var]); +match_glob([T1 | Ts1], [T2 | Ts2], Var, Dict) -> + match_glob(Ts1, Ts2, Var, match_template(T1, T2, Dict)); +match_glob([], _Group, Var, Dict) when Var =:= '_' ; Var =:= 0 -> + Dict; % anonymous glob variable +match_glob([], Group, Var, Dict) -> + orddict:store(Var, lists:reverse(Group), Dict); +match_glob(_, _, _, _Dict) -> + throw(error). % shape mismatch + + +%% compare two syntax trees for equivalence +compare_trees(T1, T2) -> + Type1 = type(T1), + case type(T2) of + Type1 -> + case subtrees(T1) of + [] -> + case subtrees(T2) of + [] -> compare_leaves(Type1, T1, T2); + _Gs2 -> false % shape mismatch + end; + Gs1 -> + case subtrees(T2) of + [] -> false; % shape mismatch + Gs2 -> compare_trees_1(Gs1, Gs2) + end + end; + _Type2 -> + false % different tree types + end. + +compare_trees_1([G1 | Gs1], [G2 | Gs2]) -> + compare_trees_2(G1, G2) andalso compare_trees_1(Gs1, Gs2); +compare_trees_1([], []) -> + true; +compare_trees_1(_, _) -> + false. % shape mismatch + +compare_trees_2([T1 | Ts1], [T2 | Ts2]) -> + compare_trees(T1, T2) andalso compare_trees_2(Ts1, Ts2); +compare_trees_2([], []) -> + true; +compare_trees_2(_, _) -> + false. % shape mismatch + +compare_leaves(Type, T1, T2) -> + case Type of + atom -> + erl_syntax:atom_value(T1) + =:= erl_syntax:atom_value(T2); + char -> + erl_syntax:char_value(T1) + =:= erl_syntax:char_value(T2); + float -> + erl_syntax:float_value(T1) + =:= erl_syntax:float_value(T2); + integer -> + erl_syntax:integer_value(T1) + =:= erl_syntax:integer_value(T2); + string -> + erl_syntax:string_value(T1) + =:= erl_syntax:string_value(T2); + operator -> + erl_syntax:operator_name(T1) + =:= erl_syntax:operator_name(T2); + text -> + erl_syntax:text_string(T1) + =:= erl_syntax:text_string(T2); + variable -> + erl_syntax:variable_name(T1) + =:= erl_syntax:variable_name(T2); + _ -> + true % trivially equal nodes + end. + + +%% @doc Match against one or more clauses with patterns and optional guards. +%% +%% Note that clauses following a default action will be ignored. +%% +%% @see match/2 + +-type switch_clause() :: + {pattern_or_patterns(), guarded_actions()} + | {pattern_or_patterns(), guard_test(), switch_action()} + | default_action(). + +-type guarded_actions() :: guarded_action() | [guarded_action()]. + +-type guarded_action() :: switch_action() | {guard_test(), switch_action()}. + +-type switch_action() :: fun( (env()) -> any() ). + +-type guard_test() :: fun( (env()) -> boolean() ). + +-type default_action() :: fun( () -> any() ). + + +-spec switch(tree_or_trees(), [switch_clause()]) -> any(). + +switch(Trees, [{Patterns, GuardedActions} | Cs]) when is_list(GuardedActions) -> + switch_1(Trees, Patterns, GuardedActions, Cs); +switch(Trees, [{Patterns, GuardedAction} | Cs]) -> + switch_1(Trees, Patterns, [GuardedAction], Cs); +switch(Trees, [{Patterns, Guard, Action} | Cs]) -> + switch_1(Trees, Patterns, [{Guard, Action}], Cs); +switch(_Trees, [Default | _Cs]) when is_function(Default, 0) -> + Default(); +switch(_Trees, []) -> + erlang:error(merl_switch_clause); +switch(_Tree, _) -> + erlang:error(merl_switch_badarg). + +switch_1(Trees, Patterns, GuardedActions, Cs) -> + case match(Patterns, Trees) of + {ok, Env} -> + switch_2(Env, GuardedActions, Trees, Cs); + error -> + switch(Trees, Cs) + end. + +switch_2(Env, [{Guard, Action} | Bs], Trees, Cs) + when is_function(Guard, 1), is_function(Action, 1) -> + case Guard(Env) of + true -> Action(Env); + false -> switch_2(Env, Bs, Trees, Cs) + end; +switch_2(Env, [Action | _Bs], _Trees, _Cs) when is_function(Action, 1) -> + Action(Env); +switch_2(_Env, [], Trees, Cs) -> + switch(Trees, Cs); +switch_2(_Env, _, _Trees, _Cs) -> + erlang:error(merl_switch_badarg). + + +%% ------------------------------------------------------------------------ +%% Internal utility functions + +-dialyzer({nowarn_function, fail/1}). % no local return + +fail(Text) -> + fail(Text, []). + +fail(Fs, As) -> + throw({error, lists:flatten(io_lib:format(Fs, As))}). + +flatten_text([L | _]=Lines) when is_list(L) -> + lists:foldr(fun(S, T) -> S ++ [$\n | T] end, "", Lines); +flatten_text([B | _]=Lines) when is_binary(B) -> + lists:foldr(fun(S, T) -> binary_to_list(S) ++ [$\n | T] end, "", Lines); +flatten_text(Text) when is_binary(Text) -> + binary_to_list(Text); +flatten_text(Text) -> + Text. + +-spec metavar(tree()) -> {string()} | false. + +%% Check if a syntax tree represents a metavariable. If not, 'false' is +%% returned; otherwise, this returns a 1-tuple with a string containing the +%% variable name including lift/glob prefixes but without any leading +%% metavariable prefix, and instead prefixed with "v" for a variable or "i" +%% for an integer. +%% +%% Metavariables are atoms starting with @, variables starting with _@, +%% strings starting with "'@, or integers starting with 909. Following the +%% prefix, one or more _ or 0 characters (unless it's the last character in +%% the name) may be used to indicate "lifting" of the variable one or more +%% levels , and after that, a @ or 9 character indicates a glob metavariable +%% rather than a normal metavariable. If the name after the prefix is _ or +%% 0, the variable is treated as an anonymous catch-all pattern in matches. + +metavar(Tree) -> + case type(Tree) of + atom -> + case erl_syntax:atom_name(Tree) of + "@" ++ Cs when Cs =/= [] -> {"v"++Cs}; + _ -> false + end; + variable -> + case erl_syntax:variable_literal(Tree) of + "_@" ++ Cs when Cs =/= [] -> {"v"++Cs}; + _ -> false + end; + integer -> + case erl_syntax:integer_value(Tree) of + N when N >= 9090 -> + case integer_to_list(N) of + "909" ++ Cs -> {"n"++Cs}; + _ -> false + end; + _ -> false + end; + string -> + case erl_syntax:string_value(Tree) of + "'@" ++ Cs -> {"v"++Cs}; + _ -> false + end; + _ -> + false + end. + +%% wrappers around erl_syntax functions to provide more uniform shape of +%% generic subtrees (maybe this can be fixed in syntax_tools one day) + +type(T) -> + case erl_syntax:type(T) of + nil -> list; + Type -> Type + end. + +subtrees(T) -> + case erl_syntax:type(T) of + tuple -> + [erl_syntax:tuple_elements(T)]; %% don't treat {} as a leaf + nil -> + [[], []]; %% don't treat [] as a leaf, but as a list + list -> + case erl_syntax:list_suffix(T) of + none -> + [erl_syntax:list_prefix(T), []]; + S -> + [erl_syntax:list_prefix(T), [S]] + end; + binary_field -> + [[erl_syntax:binary_field_body(T)], + erl_syntax:binary_field_types(T)]; + clause -> + case erl_syntax:clause_guard(T) of + none -> + [erl_syntax:clause_patterns(T), [], + erl_syntax:clause_body(T)]; + G -> + [erl_syntax:clause_patterns(T), [G], + erl_syntax:clause_body(T)] + end; + receive_expr -> + case erl_syntax:receive_expr_timeout(T) of + none -> + [erl_syntax:receive_expr_clauses(T), [], []]; + E -> + [erl_syntax:receive_expr_clauses(T), [E], + erl_syntax:receive_expr_action(T)] + end; + record_access -> + case erl_syntax:record_access_type(T) of + none -> + [[erl_syntax:record_access_argument(T)], [], + [erl_syntax:record_access_field(T)]]; + R -> + [[erl_syntax:record_access_argument(T)], [R], + [erl_syntax:record_access_field(T)]] + end; + record_expr -> + case erl_syntax:record_expr_argument(T) of + none -> + [[], [erl_syntax:record_expr_type(T)], + erl_syntax:record_expr_fields(T)]; + V -> + [[V], [erl_syntax:record_expr_type(T)], + erl_syntax:record_expr_fields(T)] + end; + record_field -> + case erl_syntax:record_field_value(T) of + none -> + [[erl_syntax:record_field_name(T)], []]; + V -> + [[erl_syntax:record_field_name(T)], [V]] + end; + _ -> + erl_syntax:subtrees(T) + end. + +make_tree(list, [P, []]) -> erl_syntax:list(P); +make_tree(list, [P, [S]]) -> erl_syntax:list(P, S); +make_tree(tuple, [E]) -> erl_syntax:tuple(E); +make_tree(binary_field, [[B], Ts]) -> erl_syntax:binary_field(B, Ts); +make_tree(clause, [P, [], B]) -> erl_syntax:clause(P, none, B); +make_tree(clause, [P, [G], B]) -> erl_syntax:clause(P, G, B); +make_tree(receive_expr, [C, [], _A]) -> erl_syntax:receive_expr(C); +make_tree(receive_expr, [C, [E], A]) -> erl_syntax:receive_expr(C, E, A); +make_tree(record_access, [[E], [], [F]]) -> erl_syntax:record_access(E, F); +make_tree(record_access, [[E], [T], [F]]) -> erl_syntax:record_access(E, T, F); +make_tree(record_expr, [[], [T], F]) -> erl_syntax:record_expr(T, F); +make_tree(record_expr, [[E], [T], F]) -> erl_syntax:record_expr(E, T, F); +make_tree(record_field, [[N], []]) -> erl_syntax:record_field(N); +make_tree(record_field, [[N], [E]]) -> erl_syntax:record_field(N, E); +make_tree(Type, Groups) -> + erl_syntax:make_tree(Type, Groups). + +merge_comments(_StartLine, [], [T]) -> T; +merge_comments(_StartLine, [], Ts) -> Ts; +merge_comments(StartLine, Comments, Ts) -> + merge_comments(StartLine, Comments, Ts, []). + +merge_comments(_StartLine, [], [], [T]) -> T; +merge_comments(_StartLine, [], [T], []) -> T; +merge_comments(_StartLine, [], Ts, Acc) -> + lists:reverse(Acc, Ts); +merge_comments(StartLine, Cs, [], Acc) -> + merge_comments(StartLine, [], [], + [erl_syntax:set_pos( + erl_syntax:comment(Indent, Text), + StartLine + Line - 1) + || {Line, _, Indent, Text} <- Cs] ++ Acc); +merge_comments(StartLine, [C|Cs], [T|Ts], Acc) -> + {Line, _Col, Indent, Text} = C, + CommentLine = StartLine + Line - 1, + case erl_syntax:get_pos(T) of + Pos when Pos < CommentLine -> + %% TODO: traverse sub-tree rather than only the top level nodes + merge_comments(StartLine, [C|Cs], Ts, [T|Acc]); + CommentLine -> + Tc = erl_syntax:add_postcomments( + [erl_syntax:comment(Indent, Text)], T), + merge_comments(StartLine, Cs, [Tc|Ts], Acc); + _ -> + Tc = erl_syntax:add_precomments( + [erl_syntax:comment(Indent, Text)], T), + merge_comments(StartLine, Cs, [Tc|Ts], Acc) + end. diff --git a/lib/syntax_tools/src/merl_tests.erl b/lib/syntax_tools/src/merl_tests.erl new file mode 100644 index 0000000000..c1aae3100e --- /dev/null +++ b/lib/syntax_tools/src/merl_tests.erl @@ -0,0 +1,539 @@ +%% --------------------------------------------------------------------- +%% 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 +%% +%% 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. +%% +%% @author Richard Carlsson +%% @copyright 2012-2015 Richard Carlsson +%% @doc Unit tests for merl. +%% @private + +-module(merl_tests). + +%-define(MERL_NO_TRANSFORM, true). +-include("merl.hrl"). + +-include_lib("eunit/include/eunit.hrl"). + + +%% utilities + +f(Ts) when is_list(Ts) -> + lists:flatmap(fun erl_prettypr:format/1, Ts); +f(T) -> + erl_prettypr:format(T). + +fe(Env) -> [{Key, f(T)} || {Key, T} <- Env]. + +g_exported_() -> + %% for testing the parse transform, autoexported to avoid complaints + {ok, merl:quote(?LINE, "42")}. + + +ok({ok, X}) -> X. + + +%% +%% tests +%% + +parse_error_test_() -> + [?_assertThrow({error, "1: syntax error before: '{'" ++ _}, + f(merl:quote("{"))) + ]. + +term_test_() -> + [?_assertEqual(tuple, erl_syntax:type(merl:term({}))), + ?_assertEqual("{foo, 42}", f(merl:term({foo, 42}))) + ]. + +quote_form_test_() -> + [?_assertEqual("f(X) -> {ok, X}.", + f(?Q("f(X) -> {ok, X}."))), + ?_assertEqual("-module(foo).", + f(?Q("-module(foo)."))), + ?_assertEqual("-import(bar, [f/1, g/2]).", + f(?Q("-import(bar, [f/1, g/2])."))), + ?_assertEqual(("-module(foo)." + "-export([f/1])." + "f(X) -> {ok, X}."), + f(?Q(["-module(foo).", + "-export([f/1]).", + "f(X) -> {ok, X}."]))) + ]. + +quote_term_test_() -> + [?_assertEqual("foo", + f(?Q("foo"))), + ?_assertEqual("42", + f(?Q("42"))), + ?_assertEqual("{foo, 42}", + f(?Q("{foo, 42}"))), + ?_assertEqual(("1" ++ "2" ++ "3"), + f(?Q("1, 2, 3"))), + ?_assertEqual(("foo" "42" "{}" "true"), + f(?Q("foo, 42, {}, (true)"))) + ]. + +quote_expr_test_() -> + [?_assertEqual("2 + 2", + f(?Q("2 + 2"))), + ?_assertEqual("f(foo, 42)", + f(?Q("f(foo, 42)"))), + ?_assertEqual("case X of\n a -> 1;\n b -> 2\nend", + f(?Q("case X of a -> 1; b -> 2 end"))), + ?_assertEqual(("2 + 2" ++ "f(42)" ++ "catch 22"), + f(?Q("2 + 2, f(42), catch 22"))) + ]. + +quote_try_clause_test_() -> + [?_assertEqual("(error:R) when R =/= foo -> ok", + f(?Q("error:R when R =/= foo -> ok"))), + %% note that without any context, clauses are printed as fun-clauses + ?_assertEqual(("(error:badarg) -> badarg" + "(exit:normal) -> normal" + "(_) -> other"), + f(?Q(["error:badarg -> badarg;", + "exit:normal -> normal;" + "_ -> other"]))) + ]. + +quote_fun_clause_test_() -> + [?_assertEqual("(X, Y) when X < Y -> {ok, X}", + f(?Q("(X, Y) when X < Y -> {ok, X}"))), + ?_assertEqual(("(X, Y) when X < Y -> less" + "(X, Y) when X > Y -> greater" + "(_, _) -> equal"), + f(?Q(["(X, Y) when X < Y -> less;", + "(X, Y) when X > Y -> greater;" + "(_, _) -> equal"])))]. + +quote_case_clause_test_() -> + [?_assertEqual("({X, Y}) when X < Y -> X", + f(?Q("{X, Y} when X < Y -> X"))), + ?_assertEqual(("({X, Y}) when X < Y -> -1" + "({X, Y}) when X > Y -> 1" + "(_) -> 0"), + f(?Q(["{X, Y} when X < Y -> -1;", + "{X, Y} when X > Y -> 1;" + "_ -> 0"])))]. + +quote_comment_test_() -> + [?_assertEqual("%% comment preserved\n" + "{foo, 42}", + f(?Q(["%% comment preserved", + "{foo, 42}"]))), + ?_assertEqual("{foo, 42}" + "%% comment preserved\n", + f(?Q(["{foo, 42}", + "%% comment preserved"]))), + ?_assertEqual(" % just a comment (with indent)\n", + f(?Q(" % just a comment (with indent)"))) + ]. + +metavar_test_() -> + [?_assertEqual("'@foo'", f(merl:tree(merl:template(?Q("'@foo'"))))), + ?_assertEqual("'@foo'", f(merl:tree(merl:template(?Q("_@foo"))))), + ?_assertEqual("'@foo'", f(merl:tree(merl:template(?Q("\"'@foo\""))))), + ?_assertEqual("{'@foo'}", f(merl:tree(merl:template(?Q("{_@foo}"))))), + ?_assertEqual("'@foo'", f(merl:tree(merl:template(?Q("{_@_foo}"))))), + ?_assertEqual("909123", f(merl:tree(merl:template(?Q("{9090123}"))))), + ?_assertEqual("{'@foo'}", + f(merl:tree(merl:template(?Q("{{{_@__foo}}}"))))), + ?_assertEqual("{909123}", + f(merl:tree(merl:template(?Q("{{{90900123}}}"))))), + ?_assertEqual("{'@@foo'}", + f(merl:tree(merl:template(?Q("{{{_@__@foo}}}"))))), + ?_assertEqual("{9099123}", + f(merl:tree(merl:template(?Q("{{{909009123}}}"))))) + ]. + +subst_test_() -> + [?_assertEqual("42", + f(merl:subst(?Q("_@foo"), [{foo, merl:term(42)}]))), + ?_assertEqual("'@foo'", + f(merl:subst(?Q("_@foo"), []))), + ?_assertEqual("{42}", + f(merl:subst(?Q("{_@foo}"), + [{foo, merl:term(42)}]))), + ?_assertEqual("{'@foo'}", + f(merl:subst(?Q("{_@foo}"), []))), + ?_assertEqual("fun bar/0", + f(merl:subst(merl:template(?Q("fun '@foo'/0")), + [{foo, merl:term(bar)}]))), + ?_assertEqual("fun foo/3", + f(merl:subst(merl:template(?Q("fun foo/9091")), + [{1, merl:term(3)}]))), + ?_assertEqual("[42]", + f(merl:subst(merl:template(?Q("[_@foo]")), + [{foo, merl:term(42)}]))), + ?_assertEqual("[foo, bar]", + f(merl:subst(merl:template(?Q("[_@foo]")), + [{foo, [merl:term(foo),merl:term(bar)]}]))), + ?_assertEqual("{fee, fie, foe, fum}", + f(merl:subst(merl:template(?Q("{fee, _@foo, fum}")), + [{foo, [merl:term(fie),merl:term(foe)]}]))), + ?_assertEqual("[foo, bar]", + f(merl:subst(merl:template(?Q("[_@@foo]")), + [{foo, [merl:term(foo),merl:term(bar)]}]))), + ?_assertEqual("{fee, fie, foe, fum}", + f(merl:subst(merl:template(?Q("{fee, _@@foo, fum}")), + [{foo, [merl:term(fie),merl:term(foe)]}]))), + ?_assertEqual("['@@foo']", + f(merl:subst(merl:template(?Q("[_@@foo]")), []))), + ?_assertEqual("foo", + f(merl:subst(merl:template(?Q("[_@_foo]")), + [{foo, merl:term(foo)}]))), + ?_assertEqual("{'@foo'}", + f(merl:subst(merl:template(?Q("{[_@_foo]}")), []))), + ?_assertEqual("{'@@foo'}", + f(merl:subst(merl:template(?Q("{[_@_@foo]}")), []))), + ?_assertEqual("-export([foo/1, bar/2]).", + f(merl:subst(merl:template(?Q("-export(['@_@foo'/0]).")), + [{foo, [erl_syntax:arity_qualifier( + merl:term(foo), + merl:term(1)), + erl_syntax:arity_qualifier( + merl:term(bar), + merl:term(2)) + ]} + ]))) + ]. + +match_test_() -> + [?_assertEqual({ok, []}, merl:match(?Q("foo"), ?Q("foo"))), + ?_assertEqual(error, merl:match(?Q("foo"), ?Q("bar"))), + ?_assertEqual({ok,[]}, merl:match(?Q("{foo,42}"), ?Q("{foo,42}"))), + ?_assertEqual(error, merl:match(?Q("{foo,42}"), ?Q("{foo,bar}"))), + ?_assertEqual({ok,[]}, merl:match(?Q("[foo,[42]]"), ?Q("[foo,[42]]"))), + ?_assertEqual(error, merl:match(?Q("[foo,[42]]"), ?Q("[foo,{42}]"))), + ?_assertEqual({ok,[]}, merl:match(?Q("[foo,[_@_]]"), + ?Q("[foo,[42]]"))), + ?_assertEqual({ok,[]}, merl:match(?Q("[foo,[9090]]"), + ?Q("[foo,[42]]"))), + ?_assertEqual({ok,[]}, merl:match(?Q("{_@_,[_@_,2]}"), + ?Q("{foo,[1,2]}"))), + ?_assertEqual(error, merl:match(?Q("{_@_,[_@_,2]}"), + ?Q("{foo,[1,3]}"))), + ?_assertEqual({ok,[]}, merl:match(?Q("[foo,[9090,9090]]"), + ?Q("[foo,[1,2]]"))), + ?_assertEqual(error, merl:match(?Q("[foo,[9090,9090]]"), + ?Q("[foo,[1,2,3]]"))), + ?_assertEqual([{foo,"42"}], + fe(ok(merl:match(?Q("_@foo"), ?Q("42"))))), + ?_assertEqual([{foo,"42"}], + fe(ok(merl:match(?Q("{_@foo}"), ?Q("{42}"))))), + ?_assertEqual([{1,"0"},{foo,"bar"}], + fe(ok(merl:match(?Q("fun '@foo'/9091"), + ?Q("fun bar/0"))))), + ?_assertEqual([{line,"17"},{text,"\"hello\""}], + fe(ok(merl:match(?Q("{_@line, _@text}"), + ?Q("{17, \"hello\"}"))))), + ?_assertEqual([{line,"17"},{text,"\"hello\""}], + fe(ok(merl:match(?Q("foo(_@line, _@text)"), + ?Q("foo(17, \"hello\")"))))), + ?_assertEqual([{foo,""}], + fe(ok(merl:match(?Q("f(_@@foo)"), + ?Q("f()"))))), + ?_assertEqual([{foo,"fee"}], + fe(ok(merl:match(?Q("f(_@@foo)"), + ?Q("f(fee)"))))), + ?_assertEqual([{foo,"feefiefum"}], + fe(ok(merl:match(?Q("f(_@@foo)"), + ?Q("f(fee, fie, fum)"))))), + ?_assertEqual([{foo,""}], + fe(ok(merl:match(?Q("[_@@foo]"), + ?Q("[]"))))), + ?_assertEqual([{foo,"fee"}], + fe(ok(merl:match(?Q("[_@@foo]"), + ?Q("[fee]"))))), + ?_assertEqual([{foo,"feefiefoefum"}], + fe(ok(merl:match(?Q("[_@@foo]"), + ?Q("[fee, fie, foe, fum]"))))), + ?_assertEqual([{foo,""}], + fe(ok(merl:match(?Q("{_@@foo}"), + ?Q("{}"))))), + ?_assertEqual([{foo,"fee"}], + fe(ok(merl:match(?Q("{_@@foo}"), + ?Q("{fee}"))))), + ?_assertEqual([{foo,"feefiefoefum"}], + fe(ok(merl:match(?Q("{_@@foo}"), + ?Q("{fee, fie, foe, fum}"))))), + ?_assertEqual([{foo,"fie"}], + fe(ok(merl:match(?Q("{fee, _@@foo}"), + ?Q("{fee, fie}"))))), + ?_assertEqual([{foo,"fiefoefum"}], + fe(ok(merl:match(?Q("{fee, _@@foo}"), + ?Q("{fee, fie, foe, fum}"))))), + ?_assertEqual([{foo,"fie"}], + fe(ok(merl:match(?Q("{_@@foo, foe, fum}"), + ?Q("{fie, foe, fum}"))))), + ?_assertEqual([{foo,"feefie"}], + fe(ok(merl:match(?Q("{_@@foo, foe, fum}"), + ?Q("{fee, fie, foe, fum}"))))), + ?_assertEqual([{foo,"fie"}], + fe(ok(merl:match(?Q("{fee, _@@foo, fum}"), + ?Q("{fee, fie, fum}"))))), + ?_assertEqual([{foo,"fiefoe"}], + fe(ok(merl:match(?Q("{fee, _@@foo, fum}"), + ?Q("{fee, fie, foe, fum}"))))), + ?_assertEqual([{foo,"fiefoe"},{post,"fum"},{pre,"fee"}], + fe(ok(merl:match(?Q("{_@pre, _@@foo, _@post}"), + ?Q("{fee, fie, foe, fum}"))))), + ?_assertThrow({error, "multiple glob variables"++_}, + fe(ok(merl:match(?Q("{_@@foo, _@@bar}"), + ?Q("{fee, fie, foe, fum}"))))), + ?_assertEqual([], + fe(ok(merl:match(?Q("{fee, _@@_}"), + ?Q("{fee, fie, foe, fum}"))))), + ?_assertEqual([], + fe(ok(merl:match(?Q("{_@@_, foe, fum}"), + ?Q("{fee, fie, foe, fum}"))))), + ?_assertEqual([{post,"fum"},{pre,"fee"}], + fe(ok(merl:match(?Q("{_@pre, _@@_, _@post}"), + ?Q("{fee, fie, foe, fum}"))))) + ]. + +switch_test_() -> + [?_assertEqual(42, merl:switch(?Q("foo"), [fun () -> 42 end])), + ?_assertEqual(17, merl:switch(?Q("foo"), [fun () -> 17 end, + fun () -> 42 end])), + ?_assertEqual(17, merl:switch(?Q("foo"), [{?Q("foo"), + fun ([]) -> 17 end}, + fun () -> 42 end])), + ?_assertEqual(17, + merl:switch(?Q("foo"), [{?Q("bar"), fun ([]) -> 0 end}, + {?Q("foo"), fun ([]) -> 17 end}, + fun () -> 42 end])), + ?_assertEqual([{foo,"17"}], + merl:switch(?Q("{foo,17}"), + [{?Q("{bar, _@foo}"), fun (_) -> 0 end}, + {?Q("{foo, _@foo}"), fun fe/1}, + fun () -> 42 end])), + ?_assertEqual(17, + merl:switch(?Q("{foo, 17}"), + [{?Q("{foo, _@foo}"), + fun ([{foo, X}]) -> f(X) =:= "17" end, + fun (_) -> 17 end}, + fun () -> 42 end])), + ?_assertEqual([{foo,"17"}], + merl:switch(?Q("{foo, 17}"), + [{?Q("{foo, _@foo}"), + fun ([{foo, X}]) -> f(X) =:= "42" end, + fun (_) -> 0 end}, + {?Q("{foo, _@foo}"), fun fe/1}, + fun () -> 42 end])), + ?_assertEqual(17, + merl:switch(?Q("{foo, 17}"), + [{?Q("{foo, _@foo}"), + [{fun ([{foo, X}]) -> f(X) =:= "17" end, + fun (_) -> 17 end}, + fun (_) -> 0 end]}, + fun () -> 42 end])), + ?_assertEqual([{foo,"17"}], + merl:switch(?Q("{foo, 17}"), + [{?Q("{foo, _@foo}"), + [{fun ([{foo, X}]) -> f(X) =:= "42" end, + fun (_) -> 0 end}, + fun fe/1]}, + fun () -> 42 end])) + ]. + +-ifndef(MERL_NO_TRANSFORM). + +inline_meta_test_() -> + [?_assertEqual("{foo}", + f(begin + Foo = ?Q("foo"), + ?Q("{_@Foo}") + end)), + ?_assertEqual("{foo, '@bar'}", + f(begin + Foo = ?Q("foo"), + ?Q("{_@Foo,_@bar}") + end)), + ?_assertEqual("{foo, '@bar'}", + f(begin + Q1 = ?Q("foo"), + ?Q("{90919,_@bar}") + end)) + ]. + +inline_meta_autoabstract_test_() -> + [?_assertEqual("{foo}", + f(begin + Foo = foo, + ?Q("{_@Foo@}") + end)), + ?_assertEqual("{foo, '@bar@'}", + f(begin + Foo = foo, + ?Q("{_@Foo@,_@bar@}") + end)), + ?_assertEqual("{foo, '@bar@'}", + f(begin + Q1 = foo, + ?Q("{909199,_@bar@}") + end)) + ]. + +meta_match_test_() -> + [?_assertEqual("{[bar], baz()}", + f(begin + Tree = ?Q("{foo, [bar], baz()}"), + ?Q("{foo, _@Bar, '@Baz'}") = Tree, + ?Q("{_@Bar, _@Baz}") + end)), + ?_assertEqual("{[bar], baz()}", + f(begin + Tree = ?Q("{foo, [bar], baz()}"), + ?Q("{foo, 90919, 90929}") = Tree, + ?Q("{_@Q1, _@Q2}") + end)), + ?_assertError({badmatch,error}, + f(begin + Tree = ?Q("{foo, [bar], baz()}"), + ?Q("{fie, _@Bar, '@Baz'}") = Tree, + ?Q("{_@Bar, _@Baz}") + end)) + ]. + +meta_case_test_() -> + [?_assertEqual("{[bar], baz()}", + f(begin + Tree = ?Q("{foo, [bar], baz()}"), + case Tree of + ?Q("{foo, _@Bar, '@Baz'}") -> ?Q("{_@Bar, _@Baz}") + end + end)), + ?_assertEqual("{foo, [bar], baz()}", + f(begin + Tree = ?Q("{foo, [bar], baz()}"), + case Tree of + ?Q("{fie, _@Bar, '@Baz'}") -> ?Q("{_@Bar, _@Baz}"); + _ -> Tree + end + end)), + ?_assertError(merl_switch_clause, + f(begin + Tree = ?Q("{foo, [bar], baz()}"), + case Tree of + ?Q("{fie, _@Bar, '@Baz'}") -> ?Q("{_@Bar, _@Baz}") + end + end)), + ?_assertEqual("{foo, 4}", + f(begin + Tree = ?Q("{foo, 3}"), + case Tree of + ?Q("{foo, _@N}") -> + N1 = erl_syntax:concrete(N) + 1, + ?Q("{foo, _@N1@}"); + _ -> Tree + end + end)), + ?_assertEqual("-export([f/4]).", + f(begin + Tree = ?Q("-export([f/3])."), + case Tree of + ?Q("-export([f/90919]).") -> + Q2 = erl_syntax:concrete(Q1) + 1, + ?Q("-export([f/909299])."); + _ -> Tree + end + end)), + ?_assertEqual("{1, [bar], baz()}", + f(begin + Tree = ?Q("{foo, [bar], baz()}"), + case Tree of + ?Q("{foo, _@Bar, '@Baz'}") -> + ?Q("{1, _@Bar, _@Baz}"); + ?Q("{fie, _@Bar, '@Baz'}") -> + ?Q("{2, _@Bar, _@Baz}"); + _ -> Tree + end + end)), + ?_assertEqual("{2, [bar], baz()}", + f(begin + Tree = ?Q("{fie, [bar], baz()}"), + case Tree of + ?Q("{foo, _@Bar, '@Baz'}") -> + ?Q("{1, _@Bar, _@Baz}"); + ?Q("{fie, _@Bar, '@Baz'}") -> + ?Q("{2, _@Bar, _@Baz}"); + _ -> Tree + end + end)), + ?_assertEqual("{2, baz()}", + f(begin + Tree = ?Q("{foo, [bar], baz()}"), + case Tree of + ?Q("{foo, [_@Bar], '@Baz'}") + when erl_syntax:is_atom(Bar, foo) -> + ?Q("{1, _@Baz}"); + ?Q("{foo, [_@Bar], '@Baz'}") + when erl_syntax:is_atom(Bar, bar) -> + ?Q("{2, _@Baz}"); + ?Q("{foo, [_@Bar], '@Baz'}") -> + ?Q("{3, _@Baz}"); + _ -> Tree + end + end)), + ?_assertEqual("{2, 42}", + f(begin + Tree = ?Q("{foo, [bar], 42}"), + case Tree of + ?Q("{foo, [_@Bar], '@Baz'}") + when erl_syntax:is_atom(Bar, bar), + erl_syntax:is_integer(Baz, 17) -> + ?Q("{1, _@Bar}"); + ?Q("{foo, [_@Bar], '@Baz'}") + when erl_syntax:is_atom(Bar, bar), + erl_syntax:is_integer(Baz, 42) -> + ?Q("{2, _@Baz}"); + ?Q("{foo, [_@Bar], '@Baz'}") -> + ?Q("{3, _@Baz}"); + _ -> Tree + end + end)), + ?_assertEqual("{2, 42}", + f(begin + Tree = ?Q("{foo, [baz], 42}"), + case Tree of + ?Q("{foo, [_@Bar], '@Baz'}") + when erl_syntax:is_atom(Bar, bar), + erl_syntax:is_integer(Baz, 17) + ; erl_syntax:is_atom(Bar, baz), + erl_syntax:is_integer(Baz, 17) -> + ?Q("{1, _@Bar}"); + ?Q("{foo, [_@Bar], '@Baz'}") + when erl_syntax:is_atom(Bar, bar), + erl_syntax:is_integer(Baz, 42) + ; erl_syntax:is_atom(Bar, baz), + erl_syntax:is_integer(Baz, 42) -> + ?Q("{2, _@Baz}"); + ?Q("{foo, [_@Bar], '@Baz'}") -> + ?Q("{3, _@Baz}"); + _ -> Tree + end + end)), + ?_assertEqual("{2, foo, Bar, Baz, Bar(), Baz()}", + f(begin + Tree = ?Q("foo(Bar, Baz) -> Bar(), Baz()."), + case Tree of + ?Q("'@Func'(_@Args) -> _@Body.") -> + ?Q("{1, _@Func, _@Args, _@Body}"); + ?Q("'@Func'(_@@Args) -> _@@Body.") -> + ?Q("{2, _@Func, _@Args, _@Body}"); + ?Q("'@Func'(_@Args, Baz) -> _@Body1, _@Body2.") -> + ?Q("{3, _@Func, _@Args, _@Body1, _@Body2}") + end + end)) + ]. + +-endif. diff --git a/lib/syntax_tools/src/merl_transform.erl b/lib/syntax_tools/src/merl_transform.erl new file mode 100644 index 0000000000..66b06c8137 --- /dev/null +++ b/lib/syntax_tools/src/merl_transform.erl @@ -0,0 +1,262 @@ +%% --------------------------------------------------------------------- +%% 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 +%% +%% 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. +%% +%% @author Richard Carlsson +%% @copyright 2012-2015 Richard Carlsson +%% @doc Parse transform for merl. Enables the use of automatic metavariables +%% and using quasi-quotes in matches and case switches. Also optimizes calls +%% to functions in `merl' by partially evaluating them, turning strings to +%% templates, etc., at compile-time. +%% +%% Using `-include_lib("syntax_tools/include/merl.hrl").' enables this +%% transform, unless the macro `MERL_NO_TRANSFORM' is defined first. + +-module(merl_transform). + +-export([parse_transform/2]). + +%% NOTE: We cannot use inline metavariables or any other parse transform +%% features in this module, because it must be possible to compile it with +%% the parse transform disabled! +-include("merl.hrl"). + +%% TODO: unroll calls to switch? it will probably get messy + +%% TODO: use Igor to make resulting code independent of merl at runtime? + +parse_transform(Forms, _Options) -> + erl_syntax:revert_forms(expand(erl_syntax:form_list(Forms))). + +expand(Tree0) -> + Tree = pre(Tree0), + post(case erl_syntax:subtrees(Tree) of + [] -> + Tree; + Gs -> + erl_syntax:update_tree(Tree, + [[expand(T) || T <- G] || G <- Gs]) + end). + +pre(T) -> + merl:switch( + T, + [{?Q("merl:quote(_@line, _@text) = _@expr"), + fun ([{expr,_}, {line,Line}, {text,Text}]) -> + erl_syntax:is_literal(Text) andalso erl_syntax:is_literal(Line) + end, + fun ([{expr,Expr}, {line,Line}, {text,Text}]) -> + pre_expand_match(Expr, erl_syntax:concrete(Line), + erl_syntax:concrete(Text)) + end}, + {?Q(["case _@expr of", + " merl:quote(_@_, _@text) when _@__@_ -> _@@_; _@_@_ -> 0", + "end"]), + fun case_guard/1, + fun (As) -> case_body(As, T) end}, + fun () -> T end + ]). + +case_guard([{expr,_}, {text,Text}]) -> + erl_syntax:is_literal(Text). + +case_body([{expr,Expr}, {text,_Text}], T) -> + pre_expand_case(Expr, erl_syntax:case_expr_clauses(T), + erl_syntax:get_pos(T)). + +post(T) -> + merl:switch( + T, + [{?Q("merl:_@function(_@@args)"), + [{fun ([{args, As}, {function, F}]) -> + lists:all(fun erl_syntax:is_literal/1, [F|As]) + end, + fun ([{args, As}, {function, F}]) -> + Line = erl_syntax:get_pos(F), + [F1|As1] = lists:map(fun erl_syntax:concrete/1, [F|As]), + eval_call(Line, F1, As1, T) + end}, + fun ([{args, As}, {function, F}]) -> + merl:switch( + F, + [{?Q("qquote"), fun ([]) -> expand_qquote(As, T, 1) end}, + {?Q("subst"), fun ([]) -> expand_template(F, As, T) end}, + {?Q("match"), fun ([]) -> expand_template(F, As, T) end}, + fun () -> T end + ]) + end]}, + fun () -> T end]). + +expand_qquote([Line, Text, Env], T, _) -> + case erl_syntax:is_literal(Line) of + true -> + expand_qquote([Text, Env], T, erl_syntax:concrete(Line)); + false -> + T + end; +expand_qquote([Text, Env], T, Line) -> + case erl_syntax:is_literal(Text) of + true -> + As = [Line, erl_syntax:concrete(Text)], + %% expand further if possible + expand(merl:qquote(Line, "merl:subst(_@tree, _@env)", + [{tree, eval_call(Line, quote, As, T)}, + {env, Env}])); + false -> + T + end; +expand_qquote(_As, T, _StartPos) -> + T. + +expand_template(F, [Pattern | Args], T) -> + case erl_syntax:is_literal(Pattern) of + true -> + Line = erl_syntax:get_pos(Pattern), + As = [erl_syntax:concrete(Pattern)], + merl:qquote(Line, "merl:_@function(_@pattern, _@args)", + [{function, F}, + {pattern, eval_call(Line, template, As, T)}, + {args, Args}]); + false -> + T + end; +expand_template(_F, _As, T) -> + T. + +eval_call(Line, F, As, T) -> + try apply(merl, F, As) of + T1 when F =:= quote -> + %% lift metavariables in a template to Erlang variables + Template = merl:template(T1), + Vars = merl:template_vars(Template), + case lists:any(fun is_inline_metavar/1, Vars) of + true when is_list(T1) -> + merl:qquote(Line, "merl:tree([_@template])", + [{template, merl:meta_template(Template)}]); + true -> + merl:qquote(Line, "merl:tree(_@template)", + [{template, merl:meta_template(Template)}]); + false -> + merl:term(T1) + end; + T1 -> + merl:term(T1) + catch + throw:_Reason -> T + end. + +pre_expand_match(Expr, Line, Text) -> + {Template, Out, _Vars} = rewrite_pattern(Line, Text), + merl:qquote(Line, "{ok, _@out} = merl:match(_@template, _@expr)", + [{expr, Expr}, + {out, Out}, + {template, erl_syntax:abstract(Template)}]). + +rewrite_pattern(Line, Text) -> + %% we must rewrite the metavariables in the pattern to use lowercase, + %% and then use real matching to bind the Erlang-level variables + T0 = merl:template(merl:quote(Line, Text)), + Vars = [V || V <- merl:template_vars(T0), is_inline_metavar(V)], + {merl:alpha(T0, [{V, var_to_tag(V)} || V <- Vars]), + erl_syntax:list([erl_syntax:tuple([erl_syntax:abstract(var_to_tag(V)), + erl_syntax:variable(var_name(V))]) + || V <- Vars]), + Vars}. + +var_name(V) when is_integer(V) -> + V1 = if V > 99, (V rem 100) =:= 99 -> + V div 100; + V > 9, (V rem 10) =:= 9 -> + V div 10; + true -> V + end, + list_to_atom("Q" ++ integer_to_list(V1)); +var_name(V) -> V. + +var_to_tag(V) when is_integer(V) -> V; +var_to_tag(V) -> + list_to_atom(string:to_lower(atom_to_list(V))). + +pre_expand_case(Expr, Clauses, Line) -> + merl:qquote(Line, "merl:switch(_@expr, _@clauses)", + [{clauses, erl_syntax:list([pre_expand_case_clause(C) + || C <- Clauses])}, + {expr, Expr}]). + +pre_expand_case_clause(T) -> + %% note that the only allowed non ``?Q(...) -> ...'' clause is ``_ -> ...'' + merl:switch( + T, + [{?Q("(merl:quote(_@line, _@text)) when _@__@guard -> _@@body"), + fun ([{body,_}, {guard,_}, {line,Line}, {text,Text}]) -> + erl_syntax:is_literal(Text) andalso erl_syntax:is_literal(Line) + end, + fun ([{body,Body}, {guard,Guard}, {line,Line}, {text,Text}]) -> + pre_expand_case_clause(Body, Guard, erl_syntax:concrete(Line), + erl_syntax:concrete(Text)) + end}, + {?Q("_ -> _@@body"), + fun (Env) -> merl:qquote("fun () -> _@body end", Env) end} + ]). + +pre_expand_case_clause(Body, Guard, Line, Text) -> + %% this is similar to a meta-match ``?Q("...") = Term'' + %% (note that the guards may in fact be arbitrary expressions) + {Template, Out, Vars} = rewrite_pattern(Line, Text), + GuardExprs = rewrite_guard(Guard), + Param = [{body, Body}, + {guard,GuardExprs}, + {out, Out}, + {template, erl_syntax:abstract(Template)}, + {unused, dummy_uses(Vars)}], + case GuardExprs of + [] -> + merl:qquote(Line, ["{_@template, ", + " fun (_@out) -> _@unused, _@body end}"], + Param); + _ -> + merl:qquote(Line, ["{_@template, ", + " fun (_@out) -> _@unused, _@guard end, ", + " fun (_@out) -> _@unused, _@body end}"], + Param) + end. + +%% We have to insert dummy variable uses at the beginning of the "guard" and +%% "body" function bodies to avoid warnings for unused variables in the +%% generated code. (Expansions at the Erlang level can't be marked up as +%% compiler generated to allow later compiler stages to ignore them.) +dummy_uses(Vars) -> + [?Q("_ = _@var", [{var, erl_syntax:variable(var_name(V))}]) + || V <- Vars]. + +rewrite_guard([]) -> []; +rewrite_guard([D]) -> [make_orelse(erl_syntax:disjunction_body(D))]. + +make_orelse([]) -> []; +make_orelse([C]) -> make_andalso(erl_syntax:conjunction_body(C)); +make_orelse([C | Cs]) -> + ?Q("_@expr orelse _@rest", + [{expr, make_andalso(erl_syntax:conjunction_body(C))}, + {rest, make_orelse(Cs)}]). + +make_andalso([E]) -> E; +make_andalso([E | Es]) -> + ?Q("_@expr andalso _@rest", [{expr, E}, {rest, make_andalso(Es)}]). + +is_inline_metavar(Var) when is_atom(Var) -> + is_erlang_var(atom_to_list(Var)); +is_inline_metavar(Var) when is_integer(Var) -> + Var > 9 andalso (Var rem 10) =:= 9; +is_inline_metavar(_) -> false. + +is_erlang_var([C|_]) when C >= $A, C =< $Z ; C >= $À, C =< $Þ, C /= $× -> + true; +is_erlang_var(_) -> + false. diff --git a/lib/syntax_tools/src/syntax_tools.app.src b/lib/syntax_tools/src/syntax_tools.app.src index 83dcb5fe23..4960d2f751 100644 --- a/lib/syntax_tools/src/syntax_tools.app.src +++ b/lib/syntax_tools/src/syntax_tools.app.src @@ -11,6 +11,8 @@ erl_syntax_lib, erl_tidy, igor, + merl, + merl_transform, prettypr]}, {registered,[]}, {applications, [stdlib]}, -- cgit v1.2.3 From 279fe010040ad1e09f67a509596cf4227afe658d Mon Sep 17 00:00:00 2001 From: Richard Carlsson Date: Tue, 12 May 2015 10:48:45 +0200 Subject: Make merl compatible with OTP 18.0 --- lib/syntax_tools/src/merl.erl | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/merl.erl b/lib/syntax_tools/src/merl.erl index 1e7ccc525a..690306c17b 100644 --- a/lib/syntax_tools/src/merl.erl +++ b/lib/syntax_tools/src/merl.erl @@ -300,7 +300,7 @@ %% while a flat string or binary represents source code containing newlines. -type text() :: string() | binary() | [string()] | [binary()]. --type location() :: erl_scan:location(). +-type location() :: erl_anno:location(). %% ------------------------------------------------------------------------ @@ -1162,15 +1162,6 @@ subtrees(T) -> [erl_syntax:receive_expr_clauses(T), [E], erl_syntax:receive_expr_action(T)] end; - record_access -> - case erl_syntax:record_access_type(T) of - none -> - [[erl_syntax:record_access_argument(T)], [], - [erl_syntax:record_access_field(T)]]; - R -> - [[erl_syntax:record_access_argument(T)], [R], - [erl_syntax:record_access_field(T)]] - end; record_expr -> case erl_syntax:record_expr_argument(T) of none -> @@ -1199,8 +1190,6 @@ make_tree(clause, [P, [], B]) -> erl_syntax:clause(P, none, B); make_tree(clause, [P, [G], B]) -> erl_syntax:clause(P, G, B); make_tree(receive_expr, [C, [], _A]) -> erl_syntax:receive_expr(C); make_tree(receive_expr, [C, [E], A]) -> erl_syntax:receive_expr(C, E, A); -make_tree(record_access, [[E], [], [F]]) -> erl_syntax:record_access(E, F); -make_tree(record_access, [[E], [T], [F]]) -> erl_syntax:record_access(E, T, F); make_tree(record_expr, [[], [T], F]) -> erl_syntax:record_expr(T, F); make_tree(record_expr, [[E], [T], F]) -> erl_syntax:record_expr(E, T, F); make_tree(record_field, [[N], []]) -> erl_syntax:record_field(N); -- cgit v1.2.3 From 2c2260044637b8acd706cf3a784568e27340e61e Mon Sep 17 00:00:00 2001 From: Hans Bolinder Date: Tue, 19 May 2015 11:57:11 +0200 Subject: Update runtime_dependencies in application resource files Applications that use the new erl_anno module are depending on STDLIB 2.5. Note that CosNotification, Megaco, SNMP, Xmerl, and Parsetools use the erl_anno module via the Yecc parsers only (the header file in lib/parsetools/include/yeccpre.hrl calls the erl_anno module). HiPE does not call the erl_anno module, but uses an exported type. We have chosen to make HiPE dependent on the erl_anno module. --- lib/syntax_tools/src/syntax_tools.app.src | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/syntax_tools.app.src b/lib/syntax_tools/src/syntax_tools.app.src index 83dcb5fe23..0d8d100d9c 100644 --- a/lib/syntax_tools/src/syntax_tools.app.src +++ b/lib/syntax_tools/src/syntax_tools.app.src @@ -15,4 +15,4 @@ {registered,[]}, {applications, [stdlib]}, {env, []}, - {runtime_dependencies, ["stdlib-2.0","kernel-3.0","erts-6.0"]}]}. + {runtime_dependencies, ["stdlib-2.5","kernel-3.0","erts-6.0"]}]}. -- cgit v1.2.3 From a33d8b3d5fc38deabd4823eb3aabc0fddca95e33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Thu, 28 May 2015 12:21:16 +0200 Subject: Makefile: Eliminate crash when building with native libs When building with --enable-native-libs and allowing 'make' to use multiple threads, the compiler would crash because the erl_syntax module was missing. Here is the recipe for making it crash on my computer: git clean -dxfq MAKEFLAGS='-j10' ./otp_build setup -a --enable-native-libs What would happen is that when merl_transform was used to compile itself, it would call 'merl' which in turn would call erl_syntax and erl_syntax_lib. Since those modules were not listed as dependencies, there was no guarantee that they had actually been compiled. In fact, erl_syntax is a large module so a compilation it with +native would usually not finish before it was needed for compiling merl_transform. To ensure that the erl_syntax and erl_syntax_lib modules are available when needed, they must be explicitly listed as dependencies. --- lib/syntax_tools/src/Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/Makefile b/lib/syntax_tools/src/Makefile index 2c565cee7f..2e91adf8af 100644 --- a/lib/syntax_tools/src/Makefile +++ b/lib/syntax_tools/src/Makefile @@ -75,7 +75,8 @@ $(EBIN)/%.$(EMULATOR):%.erl # special rules and dependencies to apply the transform to itself $(EBIN)/merl_transform.beam: $(EBIN)/merl.beam ./merl_transform.beam \ - ../include/merl.hrl + ../include/merl.hrl \ + $(EBIN)/erl_syntax.beam $(EBIN)/erl_syntax_lib.beam ./merl_transform.beam: ./merl_transform.erl $(EBIN)/merl.beam \ ../include/merl.hrl $(V_ERLC) -DMERL_NO_TRANSFORM $(ERL_COMPILE_FLAGS) -o ./ $< -- cgit v1.2.3 From b41830f01e38273a3fd39fd05b87cd3345d75d0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Thu, 28 May 2015 15:05:24 +0200 Subject: syntax_tools app file: Update dependencies The compiler application is now a dependency. --- lib/syntax_tools/src/syntax_tools.app.src | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/syntax_tools.app.src b/lib/syntax_tools/src/syntax_tools.app.src index e207901def..dd4ac46055 100644 --- a/lib/syntax_tools/src/syntax_tools.app.src +++ b/lib/syntax_tools/src/syntax_tools.app.src @@ -17,4 +17,5 @@ {registered,[]}, {applications, [stdlib]}, {env, []}, - {runtime_dependencies, ["stdlib-2.5","kernel-3.0","erts-6.0"]}]}. + {runtime_dependencies, + ["compiler-6.0","erts-6.0","kernel-3.0","stdlib-2.5"]}]}. -- cgit v1.2.3 From 738c34d4bb8f1a3811acd00af8c6c12107f8315b Mon Sep 17 00:00:00 2001 From: Bruce Yinhe Date: Thu, 18 Jun 2015 11:31:02 +0200 Subject: Change license text to APLv2 --- lib/syntax_tools/src/syntax_tools.appup.src | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/syntax_tools.appup.src b/lib/syntax_tools/src/syntax_tools.appup.src index 89c25d14d7..59d591a01f 100644 --- a/lib/syntax_tools/src/syntax_tools.appup.src +++ b/lib/syntax_tools/src/syntax_tools.appup.src @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2014. 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/. +%% 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 %% -%% 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. +%% 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. %% %% %CopyrightEnd% {"%VSN%", -- cgit v1.2.3 From d16c327bd35493a3687f7e07e2b332d2d18c5bc8 Mon Sep 17 00:00:00 2001 From: Hans Bolinder Date: Thu, 2 Jul 2015 13:28:46 +0200 Subject: syntax_tools: Use the erl_anno module a bit more --- lib/syntax_tools/src/erl_syntax.erl | 2 +- lib/syntax_tools/src/merl.erl | 22 ++++++++++++++++------ lib/syntax_tools/src/merl_transform.erl | 16 ++++++++++++---- 3 files changed, 29 insertions(+), 11 deletions(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl index 3f2a3e05dd..97b5797b06 100644 --- a/lib/syntax_tools/src/erl_syntax.erl +++ b/lib/syntax_tools/src/erl_syntax.erl @@ -355,7 +355,7 @@ %% where `Pos' `Ann' and `Comments' are the corresponding values of a %% `tree' or `wrapper' record. --record(attr, {pos = 0 :: term(), +-record(attr, {pos = erl_anno:new(0) :: term(), ann = [] :: [term()], com = none :: 'none' | #com{}}). -type syntaxTreeAttributes() :: #attr{}. diff --git a/lib/syntax_tools/src/merl.erl b/lib/syntax_tools/src/merl.erl index 690306c17b..163ce48bbc 100644 --- a/lib/syntax_tools/src/merl.erl +++ b/lib/syntax_tools/src/merl.erl @@ -514,15 +514,17 @@ parse_forms([]) -> parse_2(Ts) -> %% one or more comma-separated expressions? %% (recall that Ts has no dot tokens if we get to this stage) - case erl_parse:parse_exprs(Ts ++ [{dot,0}]) of + A = a0(), + case erl_parse:parse_exprs(Ts ++ [{dot,A}]) of {ok, Exprs} -> Exprs; {error, E} -> - parse_3(Ts ++ [{'end',0}, {dot,0}], [E]) + parse_3(Ts ++ [{'end',A}, {dot,A}], [E]) end. parse_3(Ts, Es) -> %% try-clause or clauses? - case erl_parse:parse_exprs([{'try',0}, {atom,0,true}, {'catch',0} | Ts]) of + A = a0(), + case erl_parse:parse_exprs([{'try',A}, {atom,A,true}, {'catch',A} | Ts]) of {ok, [{'try',_,_,_,_,_}=X]} -> %% get the right kind of qualifiers in the clause patterns erl_syntax:try_expr_handlers(X); @@ -533,7 +535,8 @@ parse_3(Ts, Es) -> parse_4(Ts, Es) -> %% fun-clause or clauses? (`(a)' is also a pattern, but `(a,b)' isn't, %% so fun-clauses must be tried before normal case-clauses - case erl_parse:parse_exprs([{'fun',0} | Ts]) of + A = a0(), + case erl_parse:parse_exprs([{'fun',A} | Ts]) of {ok, [{'fun',_,{clauses,Cs}}]} -> Cs; {error, E} -> parse_5(Ts, [E|Es]) @@ -541,7 +544,8 @@ parse_4(Ts, Es) -> parse_5(Ts, Es) -> %% case-clause or clauses? - case erl_parse:parse_exprs([{'case',0}, {atom,0,true}, {'of',0} | Ts]) of + A = a0(), + case erl_parse:parse_exprs([{'case',A}, {atom,A,true}, {'of',A} | Ts]) of {ok, [{'case',_,_,Cs}]} -> Cs; {error, E} -> %% select the best error to report @@ -1210,7 +1214,7 @@ merge_comments(StartLine, Cs, [], Acc) -> merge_comments(StartLine, [], [], [erl_syntax:set_pos( erl_syntax:comment(Indent, Text), - StartLine + Line - 1) + anno(StartLine + Line - 1)) || {Line, _, Indent, Text} <- Cs] ++ Acc); merge_comments(StartLine, [C|Cs], [T|Ts], Acc) -> {Line, _Col, Indent, Text} = C, @@ -1228,3 +1232,9 @@ merge_comments(StartLine, [C|Cs], [T|Ts], Acc) -> [erl_syntax:comment(Indent, Text)], T), merge_comments(StartLine, Cs, [Tc|Ts], Acc) end. + +a0() -> + anno(0). + +anno(Location) -> + erl_anno:new(Location). diff --git a/lib/syntax_tools/src/merl_transform.erl b/lib/syntax_tools/src/merl_transform.erl index 66b06c8137..fe58b6a122 100644 --- a/lib/syntax_tools/src/merl_transform.erl +++ b/lib/syntax_tools/src/merl_transform.erl @@ -68,8 +68,7 @@ case_guard([{expr,_}, {text,Text}]) -> erl_syntax:is_literal(Text). case_body([{expr,Expr}, {text,_Text}], T) -> - pre_expand_case(Expr, erl_syntax:case_expr_clauses(T), - erl_syntax:get_pos(T)). + pre_expand_case(Expr, erl_syntax:case_expr_clauses(T), get_location(T)). post(T) -> merl:switch( @@ -79,7 +78,7 @@ post(T) -> lists:all(fun erl_syntax:is_literal/1, [F|As]) end, fun ([{args, As}, {function, F}]) -> - Line = erl_syntax:get_pos(F), + Line = get_location(F), [F1|As1] = lists:map(fun erl_syntax:concrete/1, [F|As]), eval_call(Line, F1, As1, T) end}, @@ -118,7 +117,7 @@ expand_qquote(_As, T, _StartPos) -> expand_template(F, [Pattern | Args], T) -> case erl_syntax:is_literal(Pattern) of true -> - Line = erl_syntax:get_pos(Pattern), + Line = get_location(Pattern), As = [erl_syntax:concrete(Pattern)], merl:qquote(Line, "merl:_@function(_@pattern, _@args)", [{function, F}, @@ -260,3 +259,12 @@ is_erlang_var([C|_]) when C >= $A, C =< $Z ; C >= $À, C =< $Þ, C /= $× -> true; is_erlang_var(_) -> false. + +get_location(T) -> + Pos = erl_syntax:get_pos(T), + case erl_anno:is_anno(Pos) of + true -> + erl_anno:location(Pos); + false -> + Pos + end. -- cgit v1.2.3 From 99dd169e4c499c78dd85c6e34296fdc150e47d24 Mon Sep 17 00:00:00 2001 From: Hans Bolinder Date: Tue, 29 Sep 2015 09:41:06 +0200 Subject: Update Syntax Tools Record field types have been modified due to commit 8ce35b2: "Take out automatic insertion of 'undefined' from typed record fields". --- lib/syntax_tools/src/erl_tidy.erl | 10 +++++----- lib/syntax_tools/src/igor.erl | 5 +++-- 2 files changed, 8 insertions(+), 7 deletions(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_tidy.erl b/lib/syntax_tools/src/erl_tidy.erl index db7f0939a3..f2de12b410 100644 --- a/lib/syntax_tools/src/erl_tidy.erl +++ b/lib/syntax_tools/src/erl_tidy.erl @@ -937,7 +937,7 @@ hidden_uses_2(Tree, Used) -> -record(env, {file :: file:filename(), module :: atom(), - current :: fa(), + current :: fa() | 'undefined', imports = dict:new() :: dict:dict(atom(), atom()), context = normal :: context(), verbosity = 1 :: 0 | 1 | 2, @@ -949,10 +949,10 @@ hidden_uses_2(Tree, Used) -> new_guard_tests = true :: boolean(), old_guard_tests = false :: boolean()}). --record(st, {varc :: non_neg_integer(), +-record(st, {varc :: non_neg_integer() | 'undefined', used = sets:new() :: sets:set({atom(), arity()}), imported :: sets:set({atom(), arity()}), - vars :: sets:set(atom()), + vars :: sets:set(atom()) | 'undefined', functions :: sets:set({atom(), arity()}), new_forms = [] :: [erl_syntax:syntaxTree()], rename :: dict:dict(mfa(), {atom(), atom()})}). @@ -1064,13 +1064,13 @@ visit_clause(Tree, Env, St0) -> visit_infix_expr(Tree, #env{context = guard_test}, St0) -> %% Detect transition from guard test to guard expression. - visit_other(Tree, #env{context = guard_expr}, St0); + visit_other(Tree, #env{context = guard_expr, file = ""}, St0); visit_infix_expr(Tree, Env, St0) -> visit_other(Tree, Env, St0). visit_prefix_expr(Tree, #env{context = guard_test}, St0) -> %% Detect transition from guard test to guard expression. - visit_other(Tree, #env{context = guard_expr}, St0); + visit_other(Tree, #env{context = guard_expr, file = ""}, St0); visit_prefix_expr(Tree, Env, St0) -> visit_other(Tree, Env, St0). diff --git a/lib/syntax_tools/src/igor.erl b/lib/syntax_tools/src/igor.erl index eac5af5540..4557678f9d 100644 --- a/lib/syntax_tools/src/igor.erl +++ b/lib/syntax_tools/src/igor.erl @@ -1594,10 +1594,11 @@ alias_expansions_2(Modules, Table) -> preserved :: boolean(), no_headers :: boolean(), notes :: notes(), - map :: map_fun(), + map :: map_fun() | 'undefined', renaming :: fun((atom()) -> map_fun()), expand :: dict:dict({atom(), integer()}, - {atom(), {atom(), integer()}}), + {atom(), {atom(), integer()}}) + | 'undefined', redirect :: dict:dict(atom(), atom()) }). -- cgit v1.2.3 From 80757f9491c72ee262a5910e0b3b02e95b1e5f2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Fri, 4 Dec 2015 12:15:40 +0100 Subject: Use 'rand' instead of the obsolete 'random' module In most cases, we don't have to seed the random number generator, as the rand:uniform/1 takes care about that itself. --- lib/syntax_tools/src/erl_syntax_lib.erl | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_syntax_lib.erl b/lib/syntax_tools/src/erl_syntax_lib.erl index 5b5b18d15b..58c4cc5244 100644 --- a/lib/syntax_tools/src/erl_syntax_lib.erl +++ b/lib/syntax_tools/src/erl_syntax_lib.erl @@ -359,9 +359,9 @@ new_variable_name(S) -> %% within a reasonably small range relative to the number of elements in %% the set. %% -%% This function uses the module `random' to generate new +%% This function uses the module `rand' to generate new %% keys. The seed it uses may be initialized by calling -%% `random:seed/0' or `random:seed/3' before this +%% `rand:seed/1' or `rand:seed/2' before this %% function is first called. %% %% @see new_variable_name/1 @@ -404,7 +404,13 @@ start_range(S) -> %% order, but (pseudo-)randomly distributed over the range. generate(_Key, Range) -> - random:uniform(Range). % works well + _ = case rand:export_seed() of + undefined -> + rand:seed(exsplus, {753,8,73}); + _ -> + ok + end, + rand:uniform(Range). % works well %% ===================================================================== -- cgit v1.2.3 From 2bf8c4726dfdf94bd6c190f1236c33a064238e02 Mon Sep 17 00:00:00 2001 From: Hans Bolinder Date: Fri, 11 Dec 2015 12:00:36 +0100 Subject: syntax_tools: Correct a type --- lib/syntax_tools/src/erl_recomment.erl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_recomment.erl b/lib/syntax_tools/src/erl_recomment.erl index 72e1e2d2f5..5ce533285d 100644 --- a/lib/syntax_tools/src/erl_recomment.erl +++ b/lib/syntax_tools/src/erl_recomment.erl @@ -611,12 +611,15 @@ expand_comment(C) -> attrs :: erl_syntax:syntaxTreeAttributes(), precomments = [] :: [erl_syntax:syntaxTree()], postcomments = [] :: [erl_syntax:syntaxTree()], - subtrees = [] :: [erl_syntax:syntaxTree()]}). + subtrees = [] :: [extendedSyntaxTree()]}). + -record(list, {min = 0 :: integer(), max = 0 :: integer(), subtrees = [] :: [erl_syntax:syntaxTree()]}). +-type extendedSyntaxTree() :: #tree{} | #leaf{} | #list{}. + leaf_node(Min, Max, Value) -> #leaf{min = Min, max = Max, -- cgit v1.2.3 From 6664eed554974336909d3ffe03f20349cc4c38fd Mon Sep 17 00:00:00 2001 From: Henrik Nord Date: Tue, 15 Mar 2016 15:19:56 +0100 Subject: update copyright-year --- lib/syntax_tools/src/syntax_tools.appup.src | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/syntax_tools.appup.src b/lib/syntax_tools/src/syntax_tools.appup.src index 59d591a01f..0dad228ca3 100644 --- a/lib/syntax_tools/src/syntax_tools.appup.src +++ b/lib/syntax_tools/src/syntax_tools.appup.src @@ -1,7 +1,7 @@ %% -*- erlang -*- %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2014. All Rights Reserved. +%% Copyright Ericsson AB 2014-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. -- cgit v1.2.3 From fbc2d05c2659debff1c78d989b6921a3fff6037b Mon Sep 17 00:00:00 2001 From: Hans Bolinder Date: Wed, 4 May 2016 16:28:02 +0200 Subject: syntax_tools: Update runtime dependencies --- lib/syntax_tools/src/syntax_tools.app.src | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/syntax_tools.app.src b/lib/syntax_tools/src/syntax_tools.app.src index dd4ac46055..5c6008a5f0 100644 --- a/lib/syntax_tools/src/syntax_tools.app.src +++ b/lib/syntax_tools/src/syntax_tools.app.src @@ -18,4 +18,4 @@ {applications, [stdlib]}, {env, []}, {runtime_dependencies, - ["compiler-6.0","erts-6.0","kernel-3.0","stdlib-2.5"]}]}. + ["compiler-7.0","erts-8.0","kernel-5.0","stdlib-3.0"]}]}. -- cgit v1.2.3 From c9b719d7f6a34959c9ce7889c0036fe61404f97b Mon Sep 17 00:00:00 2001 From: Hans Bolinder Date: Wed, 7 Oct 2015 12:50:29 +0200 Subject: syntax_tools: Fix pretty-printing of the \x{...} syntax --- lib/syntax_tools/src/erl_prettypr.erl | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_prettypr.erl b/lib/syntax_tools/src/erl_prettypr.erl index 81272e62de..ae7811994d 100644 --- a/lib/syntax_tools/src/erl_prettypr.erl +++ b/lib/syntax_tools/src/erl_prettypr.erl @@ -27,6 +27,8 @@ -module(erl_prettypr). +-compile(export_all). + -export([format/1, format/2, best/1, best/2, layout/1, layout/2, get_ctxt_precedence/1, set_ctxt_precedence/2, get_ctxt_paperwidth/1, set_ctxt_paperwidth/2, @@ -1020,6 +1022,8 @@ split_string_1([], _N, _L, As) -> split_string_2([$^, X | Xs], N, L, As) -> split_string_1(Xs, N - 2, L - 2, [X, $^ | As]); +split_string_2([$x, ${ | Xs], N, L, As) -> + split_string_3(Xs, N - 2, L - 2, [${, $x | As]); split_string_2([X1, X2, X3 | Xs], N, L, As) when X1 >= $0, X1 =< $7, X2 >= $0, X2 =< $7, X3 >= $0, X3 =< $7 -> split_string_1(Xs, N - 3, L - 3, [X3, X2, X1 | As]); @@ -1029,6 +1033,15 @@ split_string_2([X1, X2 | Xs], N, L, As) when split_string_2([X | Xs], N, L, As) -> split_string_1(Xs, N - 1, L - 1, [X | As]). +split_string_3([$} | Xs], N, L, As) -> + split_string_1(Xs, N - 1, L - 1, [$} | As]); +split_string_3([X | Xs], N, L, As) when + X >= $0, X =< $9; X >= $a, X =< $z; X >= $A, X =< $Z -> + split_string_3(Xs, N - 1, L -1, [X | As]); +split_string_3([X | Xs], N, L, As) when + X >= $0, X =< $9 -> + split_string_1(Xs, N - 1, L -1, [X | As]). + %% Note that there is nothing in `lay_clauses' that actually requires %% that the elements have type `clause'; it just sets up the proper %% context and arranges the elements suitably for clauses. -- cgit v1.2.3 From ee802105708818a7d9a2ea05b400168574268319 Mon Sep 17 00:00:00 2001 From: Hans Bolinder Date: Thu, 22 Oct 2015 14:26:12 +0200 Subject: syntax_tools: Add support for types and specs In particular, types and specs can be pretty-printed. There are issues with macros (left behind by epp_dodger). Typed record fields are handled. Fields are represented by triples instead of two-tuples, which is an incompatible change. Some attributes (-export_type, -spec, -type, &c) have been given meaning in recent time, but the set of wild attributes (see Barklund's spec) is not changed. --- lib/syntax_tools/src/erl_prettypr.erl | 322 +++++- lib/syntax_tools/src/erl_syntax.erl | 1696 +++++++++++++++++++++++++++---- lib/syntax_tools/src/erl_syntax_lib.erl | 181 +++- 3 files changed, 1950 insertions(+), 249 deletions(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_prettypr.erl b/lib/syntax_tools/src/erl_prettypr.erl index ae7811994d..9b6c5d977e 100644 --- a/lib/syntax_tools/src/erl_prettypr.erl +++ b/lib/syntax_tools/src/erl_prettypr.erl @@ -40,7 +40,7 @@ follow/3, empty/0]). -import(erl_parse, [preop_prec/1, inop_prec/1, func_prec/0, - max_prec/0]). + max_prec/0, type_inop_prec/1, type_preop_prec/1]). -define(PADDING, 2). -define(PAPER, 80). @@ -52,7 +52,8 @@ | fun((erl_syntax:syntaxTree(), _, _) -> prettypr:document()). -type clause_t() :: 'case_expr' | 'cond_expr' | 'fun_expr' | 'if_expr' | 'receive_expr' | 'try_expr' - | {'function', prettypr:document()}. + | {'function', prettypr:document()} + | 'spec'. -record(ctxt, {prec = 0 :: integer(), sub_indent = 2 :: non_neg_integer(), @@ -537,9 +538,6 @@ lay_2(Node, Ctxt) -> As = seq(erl_syntax:application_arguments(Node), floating(text(",")), reset_prec(Ctxt), fun lay/2), -%% D1 = beside(D, beside(text("("), -%% beside(par(As), -%% floating(text(")"))))), D1 = beside(D, beside(text("("), beside(par(As), floating(text(")"))))), @@ -653,7 +651,7 @@ lay_2(Node, Ctxt) -> beside(D1, beside(text(":"), D2)); %% - %% The rest is in alphabetical order + %% The rest is in alphabetical order (except map and types) %% arity_qualifier -> @@ -668,18 +666,67 @@ lay_2(Node, Ctxt) -> %% a period. If the arguments is `none', we only output the %% attribute name, without following parentheses. Ctxt1 = reset_prec(Ctxt), - N = erl_syntax:attribute_name(Node), - D = case erl_syntax:attribute_arguments(Node) of - none -> + Args = erl_syntax:attribute_arguments(Node), + N = erl_syntax:attribute_name(Node), + D = case attribute_type(Node) of + spec -> + [SpecTuple] = Args, + [FuncName, FuncTypes] = + erl_syntax:tuple_elements(SpecTuple), + Name = + case erl_syntax:type(FuncName) of + tuple -> + case erl_syntax:tuple_elements(FuncName) of + [F0, _] -> + F0; + [M0, F0, _] -> + erl_syntax:module_qualifier(M0, + F0); + _ -> + FuncName + end; + _ -> + FuncName + end, + Types = dodge_macros(FuncTypes), + D1 = lay_clauses(erl_syntax:concrete(Types), + spec, Ctxt1), + beside(follow(lay(N, Ctxt1), + lay(Name, Ctxt1), + Ctxt1#ctxt.break_indent), + D1); + type -> + [TypeTuple] = Args, + [Name, Type0, Elements] = + erl_syntax:tuple_elements(TypeTuple), + TypeName = dodge_macros(Name), + Type = dodge_macros(Type0), + As0 = dodge_macros(Elements), + As = erl_syntax:concrete(As0), + D1 = lay_type_application(TypeName, As, Ctxt1), + D2 = lay(erl_syntax:concrete(Type), Ctxt1), + beside(follow(lay(N, Ctxt1), + beside(D1, floating(text(" :: "))), + Ctxt1#ctxt.break_indent), + D2); + Tag when Tag =:= export_type; + Tag =:= optional_callbacks -> + [FuncNs] = Args, + FuncNames = erl_syntax:concrete(dodge_macros(FuncNs)), + As = unfold_function_names(FuncNames), + beside(lay(N, Ctxt1), + beside(text("("), + beside(lay(As, Ctxt1), + floating(text(")"))))); + _ when Args =:= none -> lay(N, Ctxt1); - Args -> - As = seq(Args, floating(text(",")), Ctxt1, - fun lay/2), + _ -> + D1 = par(seq(Args, floating(text(",")), Ctxt1, + fun lay/2)), beside(lay(N, Ctxt1), beside(text("("), - beside(par(As), - floating(text(")"))))) - end, + beside(D1, floating(text(")"))))) + end, beside(floating(text("-")), beside(D, floating(text(".")))); binary -> @@ -930,6 +977,16 @@ lay_2(Node, Ctxt) -> text -> text(erl_syntax:text_string(Node)); + typed_record_field -> + {_, Prec, _} = type_inop_prec('::'), + Ctxt1 = reset_prec(Ctxt), + D1 = lay(erl_syntax:typed_record_field_body(Node), Ctxt1), + D2 = lay(erl_syntax:typed_record_field_type(Node), + set_prec(Ctxt, Prec)), + D3 = par([D1, floating(text(" ::")), D2], + Ctxt1#ctxt.break_indent), + maybe_parentheses(D3, Prec, Ctxt); + try_expr -> Ctxt1 = reset_prec(Ctxt), D1 = sep(seq(erl_syntax:try_expr_body(Node), @@ -967,9 +1024,231 @@ lay_2(Node, Ctxt) -> warning_marker -> E = erl_syntax:warning_marker_info(Node), beside(text("%% WARNING: "), - lay_error_info(E, reset_prec(Ctxt))) + lay_error_info(E, reset_prec(Ctxt))); + + %% + %% Types + %% + + annotated_type -> + {_, Prec, _} = type_inop_prec('::'), + D1 = lay(erl_syntax:annotated_type_name(Node), + reset_prec(Ctxt)), + D2 = lay(erl_syntax:annotated_type_body(Node), + set_prec(Ctxt, Prec)), + D3 = follow(beside(D1, floating(text(" ::"))), D2, + Ctxt#ctxt.break_indent), + maybe_parentheses(D3, Prec, Ctxt); + + type_application -> + Name = erl_syntax:type_application_name(Node), + Arguments = erl_syntax:type_application_arguments(Node), + %% Prefer shorthand notation. + case erl_syntax_lib:analyze_type_application(Node) of + {nil, 0} -> + text("[]"); + {list, 1} -> + [A] = Arguments, + D1 = lay(A, reset_prec(Ctxt)), + beside(text("["), beside(D1, text("]"))); + {nonempty_list, 1} -> + [A] = Arguments, + D1 = lay(A, reset_prec(Ctxt)), + beside(text("["), beside(D1, text(", ...]"))); + _ -> + lay_type_application(Name, Arguments, Ctxt) + end; + + bitstring_type -> + Ctxt1 = set_prec(Ctxt, max_prec()), + M = erl_syntax:bitstring_type_m(Node), + N = erl_syntax:bitstring_type_n(Node), + D1 = [beside(text("_:"), lay(M, Ctxt1)) || + (erl_syntax:type(M) =/= integer orelse + erl_syntax:integer_value(M) =/= 0)], + D2 = [beside(text("_:_*"), lay(N, Ctxt1)) || + (erl_syntax:type(N) =/= integer orelse + erl_syntax:integer_value(N) =/= 0)], + F = fun(D, _) -> D end, + D = seq(D1 ++ D2, floating(text(",")), Ctxt1, F), + beside(floating(text("<<")), + beside(par(D), floating(text(">>")))); + + fun_type -> + text("fun()"); + + constrained_function_type -> + Ctxt1 = reset_prec(Ctxt), + D1 = lay(erl_syntax:constrained_function_type_body(Node), + Ctxt1), + D2 = lay(erl_syntax:constrained_function_type_argument(Node), + Ctxt1), + beside(D1, + beside(floating(text(" when ")), D2)); + + function_type -> + {Before, After} = case Ctxt#ctxt.clause of + spec -> + {"", ""}; + _ -> + {"fun(", ")"} + end, + Ctxt1 = reset_prec(Ctxt), + D1 = case erl_syntax:function_type_arguments(Node) of + any_arity -> + text("(...)"); + Arguments -> + As = seq(Arguments, + floating(text(",")), Ctxt1, + fun lay/2), + beside(text("("), + beside(par(As), + floating(text(")")))) + end, + D2 = lay(erl_syntax:function_type_return(Node), Ctxt1), + beside(floating(text(Before)), + beside(D1, + beside(floating(text(" -> ")), + beside(D2, floating(text(After)))))); + + constraint -> + Name = erl_syntax:constraint_argument(Node), + Args = erl_syntax:constraint_body(Node), + case is_subtype(Name, Args) of + true -> + [Var, Type] = Args, + {PrecL, Prec, PrecR} = type_inop_prec('::'), + D1 = lay(Var, set_prec(Ctxt, PrecL)), + D2 = lay(Type, set_prec(Ctxt, PrecR)), + D3 = follow(beside(D1, floating(text(" ::"))), D2, + Ctxt#ctxt.break_indent), + maybe_parentheses(D3, Prec, Ctxt); + false -> + lay_type_application(Name, Args, Ctxt) + end; + + map_type -> + case erl_syntax:map_type_fields(Node) of + any_size -> + text("map()"); + Fs -> + {Prec, _PrecR} = type_preop_prec('#'), + Es = seq(Fs, + floating(text(",")), reset_prec(Ctxt), + fun lay/2), + D = beside(floating(text("#{")), + beside(par(Es), + floating(text("}")))), + maybe_parentheses(D, Prec, Ctxt) + end; + + map_type_pair -> + Ctxt1 = reset_prec(Ctxt), + D1 = lay(erl_syntax:map_type_pair_key(Node), Ctxt1), + D2 = lay(erl_syntax:map_type_pair_value(Node), Ctxt1), + par([D1, floating(text("=>")), D2], Ctxt1#ctxt.break_indent); + + integer_range_type -> + {PrecL, Prec, PrecR} = type_inop_prec('..'), + D1 = lay(erl_syntax:integer_range_type_low(Node), + set_prec(Ctxt, PrecL)), + D2 = lay(erl_syntax:integer_range_type_high(Node), + set_prec(Ctxt, PrecR)), + D3 = beside(D1, beside(text(".."), D2)), + maybe_parentheses(D3, Prec, Ctxt); + + record_type -> + {Prec, _PrecR} = type_preop_prec('#'), + D1 = beside(text("#"), + lay(erl_syntax:record_type_name(Node), + reset_prec(Ctxt))), + Es = seq(erl_syntax:record_type_fields(Node), + floating(text(",")), reset_prec(Ctxt), + fun lay/2), + D2 = beside(D1, + beside(text("{"), + beside(par(Es), + floating(text("}"))))), + maybe_parentheses(D2, Prec, Ctxt); + + record_type_field -> + Ctxt1 = reset_prec(Ctxt), + D1 = lay(erl_syntax:record_type_field_name(Node), Ctxt1), + D2 = lay(erl_syntax:record_type_field_type(Node), Ctxt1), + par([D1, floating(text("::")), D2], Ctxt1#ctxt.break_indent); + + tuple_type -> + case erl_syntax:tuple_type_elements(Node) of + any_size -> + text("tuple()"); + Elements -> + Es = seq(Elements, + floating(text(",")), reset_prec(Ctxt), + fun lay/2), + beside(floating(text("{")), + beside(par(Es), floating(text("}")))) + end; + + type_union -> + {_, Prec, PrecR} = type_inop_prec('|'), + Es = par(seq(erl_syntax:type_union_types(Node), + floating(text(" |")), set_prec(Ctxt, PrecR), + fun lay/2)), + maybe_parentheses(Es, Prec, Ctxt); + + user_type_application -> + lay_type_application(erl_syntax:user_type_application_name(Node), + erl_syntax:user_type_application_arguments(Node), + Ctxt) + + end. + +attribute_type(Node) -> + N = erl_syntax:attribute_name(Node), + case catch erl_syntax:concrete(N) of + opaque -> + type; + spec -> + spec; + callback -> + spec; + type -> + type; + export_type -> + export_type; + optional_callbacks -> + optional_callbacks; + _ -> + N end. +is_subtype(Name, [Var, _]) -> + (erl_syntax:is_atom(Name, is_subtype) andalso + erl_syntax:type(Var) =:= variable); +is_subtype(_, _) -> false. + +unfold_function_names(Ns) -> + F = fun ({Atom, Arity}) -> + erl_syntax:arity_qualifier(erl_syntax:atom(Atom), + erl_syntax:integer(Arity)) + end, + erl_syntax:list([F(N) || N <- Ns]). + +%% Macros are not handled well. +dodge_macros(Type) -> + F = fun (T) -> + case erl_syntax:type(T) of + macro -> + Var = erl_syntax:macro_name(T), + VarName0 = erl_syntax:variable_name(Var), + VarName = list_to_atom("?"++atom_to_list(VarName0)), + Atom = erl_syntax:atom(VarName), + Atom; + _ -> T + end + end, + erl_syntax_lib:map(F, Type). + lay_parentheses(D, _Ctxt) -> beside(floating(text("(")), beside(D, floating(text(")")))). @@ -1118,6 +1397,17 @@ lay_error_info(T, Ctxt) -> lay_concrete(T, Ctxt) -> lay(erl_syntax:abstract(T), Ctxt). +lay_type_application(Name, Arguments, Ctxt) -> + {PrecL, Prec} = func_prec(), % + D1 = lay(Name, set_prec(Ctxt, PrecL)), + As = seq(Arguments, + floating(text(",")), reset_prec(Ctxt), + fun lay/2), + D = beside(D1, beside(text("("), + beside(par(As), + floating(text(")"))))), + maybe_parentheses(D, Prec, Ctxt). + seq([H | T], Separator, Ctxt, Fun) -> case T of [] -> diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl index 97b5797b06..8ca4a8e727 100644 --- a/lib/syntax_tools/src/erl_syntax.erl +++ b/lib/syntax_tools/src/erl_syntax.erl @@ -120,6 +120,9 @@ normalize_list/1, compact_list/1, + annotated_type/2, + annotated_type_name/1, + annotated_type_body/1, application/2, application/3, application_arguments/1, @@ -150,6 +153,9 @@ binary_generator/2, binary_generator_body/1, binary_generator_pattern/1, + bitstring_type/2, + bitstring_type_m/1, + bitstring_type_n/1, block_expr/1, block_expr_body/1, case_expr/2, @@ -175,6 +181,12 @@ cond_expr_clauses/1, conjunction/1, conjunction_body/1, + constrained_function_type/2, + constrained_function_type_body/1, + constrained_function_type_argument/1, + constraint/2, + constraint_argument/1, + constraint_body/1, disjunction/1, disjunction_body/1, eof_marker/0, @@ -188,10 +200,15 @@ fun_expr/1, fun_expr_arity/1, fun_expr_clauses/1, + fun_type/0, function/2, function_arity/1, function_clauses/1, function_name/1, + function_type/1, + function_type/2, + function_type_arguments/1, + function_type_return/1, generator/2, generator_body/1, generator_pattern/1, @@ -209,6 +226,9 @@ is_integer/2, integer_value/1, integer_literal/1, + integer_range_type/2, + integer_range_type_low/1, + integer_range_type_high/1, list/1, list/2, list_comp/2, @@ -230,6 +250,12 @@ map_field_exact/2, map_field_exact_name/1, map_field_exact_value/1, + map_type/0, + map_type/1, + map_type_fields/1, + map_type_pair/2, + map_type_pair_key/1, + map_type_pair_value/1, match_expr/2, match_expr_body/1, match_expr_pattern/1, @@ -270,6 +296,12 @@ record_index_expr/2, record_index_expr_field/1, record_index_expr_type/1, + record_type/2, + record_type_name/1, + record_type_fields/1, + record_type_field/2, + record_type_field_name/1, + record_type_field_type/1, size_qualifier/2, size_qualifier_argument/1, size_qualifier_body/1, @@ -288,6 +320,18 @@ try_expr_clauses/1, try_expr_handlers/1, try_expr_after/1, + tuple_type/0, + tuple_type/1, + tuple_type_elements/1, + type_application/2, + type_application/3, + type_application_name/1, + type_application_arguments/1, + type_union/1, + type_union_types/1, + typed_record_field/2, + typed_record_field_body/1, + typed_record_field_type/1, class_qualifier/2, class_qualifier_argument/1, class_qualifier_body/1, @@ -295,6 +339,9 @@ tuple_elements/1, tuple_size/1, underscore/0, + user_type_application/2, + user_type_application_name/1, + user_type_application_arguments/1, variable/1, variable_name/1, variable_literal/1, @@ -412,23 +459,28 @@ %%
    %% %% +%% %% %% -%% %% +%% %% %% +%% +%% %% %% -%% %% %% +%% %% %% -%% %% %% +%% %% +%% +%% %% %% %% @@ -437,43 +489,57 @@ %% %% %% +%% %% +%% +%% %% %% -%% %% %% +%% %% +%% %% -%% %% +%% %% %% %% -%% %% +%% +%% +%% %% %% -%% %% +%% %% %% %% -%% %% +%% %% %% %% -%% %% +%% %% +%% +%% +%% %% %% %% -%% %% +%% %% +%% +%% +%% +%% %% +%% %% %% %% @@ -487,12 +553,14 @@ %% always have the same name as the node type itself. %% %% @see tree/2 +%% @see annotated_type/2 %% @see application/3 %% @see arity_qualifier/2 %% @see atom/1 %% @see attribute/2 %% @see binary/1 %% @see binary_field/2 +%% @see bitstring_type/2 %% @see block_expr/1 %% @see case_expr/2 %% @see catch_expr/1 @@ -502,24 +570,33 @@ %% @see comment/2 %% @see cond_expr/1 %% @see conjunction/1 +%% @see constrained_function_type/2 +%% @see constraint/2 %% @see disjunction/1 %% @see eof_marker/0 %% @see error_marker/1 %% @see float/1 %% @see form_list/1 %% @see fun_expr/1 +%% @see fun_type/0 %% @see function/2 +%% @see function_type/1 +%% @see function_type/2 %% @see generator/2 %% @see if_expr/1 %% @see implicit_fun/2 %% @see infix_expr/3 %% @see integer/1 +%% @see integer_range_type/2 %% @see list/2 %% @see list_comp/2 %% @see macro/2 %% @see map_expr/2 %% @see map_field_assoc/2 %% @see map_field_exact/2 +%% @see map_type/0 +%% @see map_type/1 +%% @see map_type_pair/2 %% @see match_expr/2 %% @see module_qualifier/2 %% @see named_fun_expr/2 @@ -532,12 +609,20 @@ %% @see record_expr/2 %% @see record_field/2 %% @see record_index_expr/2 +%% @see record_type/2 +%% @see record_type_field/2 %% @see size_qualifier/2 %% @see string/1 %% @see text/1 %% @see try_expr/3 %% @see tuple/1 +%% @see tuple_type/0 +%% @see tuple_type/1 +%% @see typed_record_field/2 +%% @see type_application/2 +%% @see type_union/1 %% @see underscore/0 +%% @see user_type_application/2 %% @see variable/1 %% @see warning_marker/1 @@ -602,6 +687,24 @@ type(Node) -> {remote, _, _, _} -> module_qualifier; {'try', _, _, _, _, _} -> try_expr; {tuple, _, _} -> tuple; + + %% Type types + {ann_type, _, _} -> annotated_type; + {remote_type, _, _} -> type_application; + {type, _, binary, [_, _]} -> bitstring_type; + {type, _, bounded_fun, [_, _]} -> constrained_function_type; + {type, _, constraint, [_, _]} -> constraint; + {type, _, 'fun', []} -> fun_type; + {type, _, 'fun', [_, _]} -> function_type; + {type, _, map, _} -> map_type; + {type, _, map_field_assoc, _} -> map_type_pair; + {type, _, record, _} -> record_type; + {type, _, field_type, _} -> record_type_field; + {type, _, range, _} -> integer_range_type; + {type, _, tuple, _} -> tuple_type; + {type, _, union, _} -> type_union; + {type, _, _, _} -> type_application; + {user_type, _, _, _} -> user_type_application; _ -> erlang:error({badarg, Node}) end. @@ -621,6 +724,7 @@ type(Node) -> %% %% %% +%% %% %% %% @@ -633,7 +737,13 @@ type(Node) -> %% %%
    applicationannotated_typearity_qualifieratomattribute
    attributebinarybinary_fieldbitstring_type
    block_exprcase_expr
    catch_exprchar
    class_qualifierclause
    commentcond_expr
    conjunctionconstrained_function_typeconstraintdisjunction
    eof_markerform_list
    fun_exprfun_typefunctionfunction_type
    generatorif_expr
    implicit_funinfix_expr
    integerinteger_range_typelist
    list_comp
    macromap_exprmap_field_assoc
    map_field_exact
    map_typemap_type_pairmatch_exprmodule_qualifiernamed_fun_expr
    named_fun_exprniloperatorparenthesesprefix_expr
    prefix_exprreceive_exprrecord_accessrecord_exprrecord_field
    record_fieldrecord_index_exprrecord_typerecord_type_field
    size_qualifierstringtext
    try_expr
    tupletuple_typetyped_record_fieldtype_applicationtype_unionunderscoreuser_type_applicationvariable
    warning_marker`error_marker'
    `float'`fun_type'`integer'`nil'`operator'
    %% +%% A node of type `map_expr' is a leaf node if and only if it has no +%% argument and no fields. +%% A node of type `map_type' is a leaf node if and only if it has no +%% fields (`any_size'). %% A node of type `tuple' is a leaf node if and only if its arity is zero. +%% A node of type `tuple_type' is a leaf node if and only if it has no +%% elements (`any_size'). %% %% Note: not all literals are leaf nodes, and vice versa. E.g., %% tuples with nonzero arity and nonempty lists may be literals, but are @@ -653,6 +763,7 @@ is_leaf(Node) -> eof_marker -> true; error_marker -> true; float -> true; + fun_type -> true; integer -> true; nil -> true; operator -> true; % nonstandard type @@ -661,7 +772,9 @@ is_leaf(Node) -> map_expr -> map_expr_fields(Node) =:= [] andalso map_expr_argument(Node) =:= none; + map_type -> map_type_fields(Node) =:= any_size; tuple -> tuple_elements(Node) =:= []; + tuple_type -> tuple_type_elements(Node) =:= any_size; underscore -> true; variable -> true; warning_marker -> true; @@ -3114,6 +3227,39 @@ attribute(Name) -> %% `Imports' is `{Module, [{A1, N1}, ..., {Ak, Nk}]}', or %% `-import(A1.....An).', if `Imports' is `[A1, ..., An]'. %% +%% {attribute, Pos, export_type, ExportedTypes} +%% +%% ExportedTypes = [{atom(), integer()}] +%% +%% Representing `-export_type([N1/A1, ..., Nk/Ak]).', +%% if `ExportedTypes' is `[{N1, A1}, ..., {Nk, Ak}]'. +%% +%% {attribute, Pos, optional_callbacks, OptionalCallbacks} +%% +%% OptionalCallbacks = [{atom(), integer()}] +%% +%% Representing `-optional_callbacks([A1/N1, ..., Ak/Nk]).', +%% if `OptionalCallbacks' is `[{A1, N1}, ..., {Ak, Nk}]'. +%% +%% {attribute, Pos, SpecTag, {FuncSpec, FuncType}} +%% +%% SpecTag = spec | callback +%% FuncSpec = {module(), atom(), arity()} | {atom(), arity()} +%% FuncType = a (possibly constrained) function type +%% +%% Representing `-SpecTag M:F/A Ft1; ...; Ftk.' or +%% `-SpecTag F/A Ft1; ...; Ftk.', if `FuncTypes' is +%% `[Ft1, ..., Ftk]'. +%% +%% {attribute, Pos, TypeTag, {Name, Type, Parameters}} +%% +%% TypeTag = type | opaque +%% Type = a type +%% Parameters = [Variable] +%% +%% Representing `-TypeTag Name(V1, ..., Vk) :: Type .' +%% if `Parameters' is `[V1, ..., Vk]'. +%% %% {attribute, Pos, file, Position} %% %% Position = {filename(), integer()} @@ -3125,13 +3271,19 @@ attribute(Name) -> %% %% Info = {Name, [Entries]} %% Name = atom() -%% Entries = {record_field, Pos, atom()} -%% | {record_field, Pos, atom(), erl_parse()} %% -%% Representing `-record(Name, {, ..., }).', if `Info' is +%% Entries = UntypedEntries +%% | {typed_record_field, UntypedEntries, Type} +%% UntypedEntries = {record_field, Pos, atom()} +%% | {record_field, Pos, atom(), erl_parse()} +%% +%% Representing `-record(Name, {, ..., }).', if `Info' is %% `{Name, [D1, ..., D1]}', where each `Fi' is either `Ai = ', %% if the corresponding `Di' is `{record_field, Pos, Ai, Ei}', or -%% otherwise simply `Ai', if `Di' is `{record_field, Pos, Ai}'. +%% otherwise simply `Ai', if `Di' is `{record_field, Pos, Ai}', or +%% `Ai = :: ', if `Di' is `{typed_record_field, +%% {record_field, Pos, Ai, Ei}, Ti}', or `Ai :: ', if `Di' is +%% `{typed_record_field, {record_field, Pos, Ai}, Ti}'. %% %% {attribute, L, Name, Term} %% @@ -3309,11 +3461,6 @@ attribute_arguments(Node) -> [set_pos( list(unfold_function_names(Data, Pos)), Pos)]; - optional_callbacks -> - D = try list(unfold_function_names(Data, Pos)) - catch _:_ -> abstract(Data) - end, - [set_pos(D, Pos)]; import -> {Module, Imports} = Data, [set_pos(atom(Module), Pos), @@ -4183,7 +4330,8 @@ record_field(Name) -> %% type(Node) = record_field %% data(Node) = #record_field{name :: Name, value :: Value} %% -%% Name = Value = syntaxTree() +%% Name = syntaxTree() +%% Value = none | syntaxTree() -spec record_field(syntaxTree(), 'none' | syntaxTree()) -> syntaxTree(). @@ -4568,7 +4716,7 @@ application(Module, Name, Arguments) -> %% %% `erl_parse' representation: %% -%% {call, Pos, Fun, Args} +%% {call, Pos, Operator, Args} %% %% Operator = erl_parse() %% Arguments = [erl_parse()] @@ -4623,316 +4771,1355 @@ application_arguments(Node) -> (data(Node1))#application.arguments end. - %% ===================================================================== -%% @doc Creates an abstract list comprehension. If `Body' is -%% `[E1, ..., En]', the result represents -%% "[Template || E1, ..., En]". +%% @doc Creates an abstract annotated type expression. The result +%% represents "Name :: Type". %% -%% @see list_comp_template/1 -%% @see list_comp_body/1 -%% @see generator/2 +%% @see annotated_type_name/1 +%% @see annotated_type_body/1 --record(list_comp, {template :: syntaxTree(), body :: [syntaxTree()]}). +-record(annotated_type, {name :: syntaxTree(), body :: syntaxTree()}). -%% type(Node) = list_comp -%% data(Node) = #list_comp{template :: Template, body :: Body} +%% type(Node) = annotated_type +%% data(Node) = #annotated_type{name :: Name, +%% body :: Type} %% -%% Template = Node = syntaxTree() -%% Body = [syntaxTree()] +%% Name = syntaxTree() +%% Type = syntaxTree() %% %% `erl_parse' representation: %% -%% {lc, Pos, Template, Body} +%% {ann_type, Pos, [Name, Type]} %% -%% Template = erl_parse() -%% Body = [erl_parse()] \ [] +%% Name = erl_parse() +%% Type = erl_parse() --spec list_comp(syntaxTree(), [syntaxTree()]) -> syntaxTree(). +-spec annotated_type(syntaxTree(), syntaxTree()) -> syntaxTree(). -list_comp(Template, Body) -> - tree(list_comp, #list_comp{template = Template, body = Body}). +annotated_type(Name, Type) -> + tree(annotated_type, #annotated_type{name = Name, body = Type}). -revert_list_comp(Node) -> +revert_annotated_type(Node) -> Pos = get_pos(Node), - Template = list_comp_template(Node), - Body = list_comp_body(Node), - {lc, Pos, Template, Body}. + Name = annotated_type_name(Node), + Type = annotated_type_body(Node), + {ann_type, Pos, [Name, Type]}. %% ===================================================================== -%% @doc Returns the template subtree of a `list_comp' node. +%% @doc Returns the name subtree of an `annotated_type' node. %% -%% @see list_comp/2 +%% @see annotated_type/2 --spec list_comp_template(syntaxTree()) -> syntaxTree(). +-spec annotated_type_name(syntaxTree()) -> syntaxTree(). -list_comp_template(Node) -> +annotated_type_name(Node) -> case unwrap(Node) of - {lc, _, Template, _} -> - Template; - Node1 -> - (data(Node1))#list_comp.template + {ann_type, _, [Name, _]} -> + Name; + Node1 -> + (data(Node1))#annotated_type.name end. %% ===================================================================== -%% @doc Returns the list of body subtrees of a `list_comp' node. +%% @doc Returns the type subtrees of an `annotated_type' node. %% -%% @see list_comp/2 +%% @see annotated_type/2 --spec list_comp_body(syntaxTree()) -> [syntaxTree()]. +-spec annotated_type_body(syntaxTree()) -> syntaxTree(). -list_comp_body(Node) -> +annotated_type_body(Node) -> case unwrap(Node) of - {lc, _, _, Body} -> - Body; - Node1 -> - (data(Node1))#list_comp.body + {ann_type, _, [_, Type]} -> + Type; + Node1 -> + (data(Node1))#annotated_type.body end. + %% ===================================================================== -%% @doc Creates an abstract binary comprehension. If `Body' is -%% `[E1, ..., En]', the result represents -%% "<<Template || E1, ..., En>>". +%% @doc Creates an abstract fun of any type. The result represents +%% "fun()". + +%% type(Node) = fun_type %% -%% @see binary_comp_template/1 -%% @see binary_comp_body/1 -%% @see generator/2 +%% `erl_parse' representation: +%% +%% {type, Pos, 'fun', []} --record(binary_comp, {template :: syntaxTree(), body :: [syntaxTree()]}). +-spec fun_type() -> syntaxTree(). -%% type(Node) = binary_comp -%% data(Node) = #binary_comp{template :: Template, body :: Body} +fun_type() -> + tree(fun_type). + +revert_fun_type(Node) -> + Pos = get_pos(Node), + {type, Pos, 'fun', []}. + + +%% ===================================================================== +%% @doc Creates an abstract type application expression. If +%% `Module' is `none', this is call is equivalent +%% to `type_application(TypeName, Arguments)', otherwise it is +%% equivalent to `type_application(module_qualifier(Module, TypeName), +%% Arguments)'. %% -%% Template = Node = syntaxTree() -%% Body = [syntaxTree()] +%% (This is a utility function.) +%% +%% @see type_application/2 +%% @see module_qualifier/2 + +-spec type_application('none' | syntaxTree(), syntaxTree(), [syntaxTree()]) -> + syntaxTree(). + +type_application(none, TypeName, Arguments) -> + type_application(TypeName, Arguments); +type_application(Module, TypeName, Arguments) -> + type_application(module_qualifier(Module, TypeName), Arguments). + + +%% ===================================================================== +%% @doc Creates an abstract type application expression. If `Arguments' is +%% `[T1, ..., Tn]', the result represents +%% "TypeName(T1, ...Tn)". +%% +%% @see user_type_application/2 +%% @see type_application/3 +%% @see type_application_name/1 +%% @see type_application_arguments/1 + +-record(type_application, {type_name :: syntaxTree(), + arguments :: [syntaxTree()]}). + +%% type(Node) = type_application +%% data(Node) = #type_application{type_name :: TypeName, +%% arguments :: Arguments} +%% +%% TypeName = syntaxTree() +%% Arguments = [syntaxTree()] %% %% `erl_parse' representation: %% -%% {bc, Pos, Template, Body} +%% {remote, Pos, [Module, Name, Arguments]} | +%% {type, Pos, Name, Arguments} %% -%% Template = erl_parse() -%% Body = [erl_parse()] \ [] +%% Module = erl_parse() +%% Name = atom() +%% Arguments = [erl_parse()] --spec binary_comp(syntaxTree(), [syntaxTree()]) -> syntaxTree(). +-spec type_application(syntaxTree(), [syntaxTree()]) -> syntaxTree(). -binary_comp(Template, Body) -> - tree(binary_comp, #binary_comp{template = Template, body = Body}). +type_application(TypeName, Arguments) -> + tree(type_application, + #type_application{type_name = TypeName, arguments = Arguments}). -revert_binary_comp(Node) -> +revert_type_application(Node) -> Pos = get_pos(Node), - Template = binary_comp_template(Node), - Body = binary_comp_body(Node), - {bc, Pos, Template, Body}. + TypeName = type_application_name(Node), + Arguments = type_application_arguments(Node), + case type(TypeName) of + module_qualifier -> + Module = module_qualifier_argument(TypeName), + Name = module_qualifier_body(TypeName), + {remote_type, Pos, [Module, Name, Arguments]}; + atom -> + {type, Pos, atom_value(TypeName), Arguments} + end. %% ===================================================================== -%% @doc Returns the template subtree of a `binary_comp' node. +%% @doc Returns the type name subtree of a `type_application' node. %% -%% @see binary_comp/2 +%% @see type_application/2 --spec binary_comp_template(syntaxTree()) -> syntaxTree(). +-spec type_application_name(syntaxTree()) -> syntaxTree(). -binary_comp_template(Node) -> +type_application_name(Node) -> case unwrap(Node) of - {bc, _, Template, _} -> - Template; - Node1 -> - (data(Node1))#binary_comp.template + {remote_type, _, [Module, Name, _]} -> + module_qualifier(Module, Name); + {type, Pos, Name, _} -> + set_pos(atom(Name), Pos); + Node1 -> + (data(Node1))#type_application.type_name end. %% ===================================================================== -%% @doc Returns the list of body subtrees of a `binary_comp' node. +%% @doc Returns the arguments subtrees of a `type_application' node. %% -%% @see binary_comp/2 +%% @see type_application/2 --spec binary_comp_body(syntaxTree()) -> [syntaxTree()]. +-spec type_application_arguments(syntaxTree()) -> [syntaxTree()]. -binary_comp_body(Node) -> +type_application_arguments(Node) -> case unwrap(Node) of - {bc, _, _, Body} -> - Body; - Node1 -> - (data(Node1))#binary_comp.body + {remote_type, _, [_, _, Arguments]} -> + Arguments; + {type, _, _, Arguments} -> + Arguments; + Node1 -> + (data(Node1))#type_application.arguments end. %% ===================================================================== -%% @doc Creates an abstract generator. The result represents -%% "Pattern <- Body". +%% @doc Creates an abstract bitstring type. The result represents +%% "<<_:M, _:_*N>>". %% -%% @see generator_pattern/1 -%% @see generator_body/1 -%% @see list_comp/2 -%% @see binary_comp/2 +%% @see bitstring_type_m/1 +%% @see bitstring_type_n/1 --record(generator, {pattern :: syntaxTree(), body :: syntaxTree()}). +-record(bitstring_type, {m :: syntaxTree(), n :: syntaxTree()}). -%% type(Node) = generator -%% data(Node) = #generator{pattern :: Pattern, body :: Body} -%% -%% Pattern = Argument = syntaxTree() +%% type(Node) = bitstring_type +%% data(Node) = #bitstring_type{m :: M, n :: N} %% -%% `erl_parse' representation: -%% -%% {generate, Pos, Pattern, Body} +%% M = syntaxTree() +%% N = syntaxTree() %% -%% Pattern = Body = erl_parse() --spec generator(syntaxTree(), syntaxTree()) -> syntaxTree(). +-spec bitstring_type(syntaxTree(), syntaxTree()) -> syntaxTree(). -generator(Pattern, Body) -> - tree(generator, #generator{pattern = Pattern, body = Body}). +bitstring_type(M, N) -> + tree(bitstring_type, #bitstring_type{m = M, n =N}). -revert_generator(Node) -> +revert_bitstring_type(Node) -> Pos = get_pos(Node), - Pattern = generator_pattern(Node), - Body = generator_body(Node), - {generate, Pos, Pattern, Body}. - + M = bitstring_type_m(Node), + N = bitstring_type_n(Node), + {type, Pos, binary, [M, N]}. %% ===================================================================== -%% @doc Returns the pattern subtree of a `generator' node. +%% @doc Returns the number of start bits, `M', of a `bitstring_type' node. %% -%% @see generator/2 +%% @see bitstring_type/2 --spec generator_pattern(syntaxTree()) -> syntaxTree(). +-spec bitstring_type_m(syntaxTree()) -> syntaxTree(). -generator_pattern(Node) -> +bitstring_type_m(Node) -> case unwrap(Node) of - {generate, _, Pattern, _} -> - Pattern; - Node1 -> - (data(Node1))#generator.pattern + {type, _, binary, [M, _]} -> + M; + Node1 -> + (data(Node1))#bitstring_type.m end. - %% ===================================================================== -%% @doc Returns the body subtree of a `generator' node. +%% @doc Returns the segment size, `N', of a `bitstring_type' node. %% -%% @see generator/2 +%% @see bitstring_type/2 --spec generator_body(syntaxTree()) -> syntaxTree(). +-spec bitstring_type_n(syntaxTree()) -> syntaxTree(). -generator_body(Node) -> +bitstring_type_n(Node) -> case unwrap(Node) of - {generate, _, _, Body} -> - Body; - Node1 -> - (data(Node1))#generator.body + {type, _, binary, [_, N]} -> + N; + Node1 -> + (data(Node1))#bitstring_type.n end. %% ===================================================================== -%% @doc Creates an abstract binary_generator. The result represents -%% "Pattern <- Body". +%% @doc Creates an abstract constrained function type. +%% If `FunctionConstraint' is `[C1, ..., Cn]', the result represents +%% "FunctionType when C1, ...Cn". %% -%% @see binary_generator_pattern/1 -%% @see binary_generator_body/1 -%% @see list_comp/2 -%% @see binary_comp/2 +%% @see constrained_function_type_body/1 +%% @see constrained_function_type_argument/1 --record(binary_generator, {pattern :: syntaxTree(), body :: syntaxTree()}). +-record(constrained_function_type, {body :: syntaxTree(), + argument :: syntaxTree()}). -%% type(Node) = binary_generator -%% data(Node) = #binary_generator{pattern :: Pattern, body :: Body} +%% type(Node) = constrained_function_type +%% data(Node) = #constrained_function_type{body :: FunctionType, +%% argument :: FunctionConstraint} %% -%% Pattern = Argument = syntaxTree() +%% FunctionType = syntaxTree() +%% FunctionConstraint = syntaxTree() %% %% `erl_parse' representation: %% -%% {b_generate, Pos, Pattern, Body} +%% {type, Pos, bounded_fun, [FunctionType, FunctionConstraint]} %% -%% Pattern = Body = erl_parse() +%% FunctionType = erl_parse() +%% FunctionConstraint = [erl_parse()] --spec binary_generator(syntaxTree(), syntaxTree()) -> syntaxTree(). +-spec constrained_function_type(syntaxTree(), [syntaxTree()]) -> syntaxTree(). -binary_generator(Pattern, Body) -> - tree(binary_generator, #binary_generator{pattern = Pattern, body = Body}). +constrained_function_type(FunctionType, FunctionConstraint) -> + Conj = conjunction(FunctionConstraint), + tree(constrained_function_type, + #constrained_function_type{body = FunctionType, + argument = Conj}). -revert_binary_generator(Node) -> +revert_constrained_function_type(Node) -> Pos = get_pos(Node), - Pattern = binary_generator_pattern(Node), - Body = binary_generator_body(Node), - {b_generate, Pos, Pattern, Body}. + FunctionType = constrained_function_type_body(Node), + FunctionConstraint = + conjunction_body(constrained_function_type_argument(Node)), + {type, Pos, bounded_fun, [FunctionType, FunctionConstraint]}. %% ===================================================================== -%% @doc Returns the pattern subtree of a `generator' node. +%% @doc Returns the function type subtree of a +%% `constrained_function_type' node. %% -%% @see binary_generator/2 +%% @see constrained_function_type/2 --spec binary_generator_pattern(syntaxTree()) -> syntaxTree(). +-spec constrained_function_type_body(syntaxTree()) -> syntaxTree(). -binary_generator_pattern(Node) -> +constrained_function_type_body(Node) -> case unwrap(Node) of - {b_generate, _, Pattern, _} -> - Pattern; - Node1 -> - (data(Node1))#binary_generator.pattern + {type, _, bounded_fun, [FunctionType, _]} -> + FunctionType; + Node1 -> + (data(Node1))#constrained_function_type.body end. - %% ===================================================================== -%% @doc Returns the body subtree of a `generator' node. +%% @doc Returns the function constraint subtree of a +%% `constrained_function_type' node. %% -%% @see binary_generator/2 +%% @see constrained_function_type/2 --spec binary_generator_body(syntaxTree()) -> syntaxTree(). +-spec constrained_function_type_argument(syntaxTree()) -> syntaxTree(). -binary_generator_body(Node) -> +constrained_function_type_argument(Node) -> case unwrap(Node) of - {b_generate, _, _, Body} -> - Body; - Node1 -> - (data(Node1))#binary_generator.body + {type, _, bounded_fun, [_, FunctionConstraint]} -> + conjunction(FunctionConstraint); + Node1 -> + (data(Node1))#constrained_function_type.argument end. %% ===================================================================== -%% @doc Creates an abstract block expression. If `Body' is -%% `[B1, ..., Bn]', the result represents "begin -%% B1, ..., Bn end". +%% @equiv function_type(any_arity, Type) + +function_type(Type) -> + function_type(any_arity, Type). + +%% ===================================================================== +%% @doc Creates an abstract function type. If `Arguments' is +%% `[T1, ..., Tn]', then if it occurs within a function +%% specification, the result represents +%% "(T1, ...Tn) -> Return"; otherwise +%% it represents +%% "fun((T1, ...Tn) -> Return)". +%% If `Arguments' is `any_arity', it represents +%% "fun((...) -> Return)". %% -%% @see block_expr_body/1 +%% Note that the `erl_parse' representation is identical for +%% "FunctionType" and +%% "fun(FunctionType)". +%% +%% @see function_type_arguments/1 +%% @see function_type_return/1 -%% type(Node) = block_expr -%% data(Node) = Body +-record(function_type, {arguments :: any_arity | [syntaxTree()], + return :: syntaxTree()}). + +%% type(Node) = function_type +%% data(Node) = #function_type{arguments :: any | Arguments, +%% return :: Type} %% -%% Body = [syntaxTree()] +%% Arguments = [syntaxTree()] +%% Type = syntaxTree() %% %% `erl_parse' representation: %% -%% {block, Pos, Body} +%% {type, Pos, 'fun', [{type, Pos, product, Arguments}, Type]} +%% {type, Pos, 'fun', [{type, Pos, any}, Type]} %% -%% Body = [erl_parse()] \ [] +%% Arguments = [erl_parse()] +%% Type = erl_parse() --spec block_expr([syntaxTree()]) -> syntaxTree(). +-spec function_type('any_arity' | syntaxTree(), syntaxTree()) -> syntaxTree(). -block_expr(Body) -> - tree(block_expr, Body). +function_type(Arguments, Return) -> + tree(function_type, + #function_type{arguments = Arguments, return = Return}). -revert_block_expr(Node) -> +revert_function_type(Node) -> Pos = get_pos(Node), - Body = block_expr_body(Node), - {block, Pos, Body}. + Type = function_type_return(Node), + case function_type_arguments(Node) of + any_arity -> + {type, Pos, 'fun', [{type, Pos, any}, Type]}; + Arguments -> + {type, Pos, 'fun', [{type, Pos, product, Arguments}, Type]} + end. %% ===================================================================== -%% @doc Returns the list of body subtrees of a `block_expr' node. +%% @doc Returns the argument types subtrees of a `function_type' node. +%% If `Node' represents "fun((...) -> Return)", +%% `any_arity' is returned; otherwise, if `Node' represents +%% "(T1, ...Tn) -> Return" or +%% "fun((T1, ...Tn) -> Return)", +%% `[T1, ..., Tn]' is returned. + %% -%% @see block_expr/1 +%% @see function_type/1 +%% @see function_type/2 --spec block_expr_body(syntaxTree()) -> [syntaxTree()]. +-spec function_type_arguments(syntaxTree()) -> any_arity | syntaxTree(). -block_expr_body(Node) -> +function_type_arguments(Node) -> case unwrap(Node) of - {block, _, Body} -> - Body; - Node1 -> - data(Node1) + {type, _, 'fun', [{type, _, any}, _]} -> + any_arity; + {type, _, 'fun', [{type, _, product, Arguments}, _]} -> + Arguments; + Node1 -> + (data(Node1))#function_type.arguments + end. + +%% ===================================================================== +%% @doc Returns the return type subtrees of a `function_type' node. +%% +%% @see function_type/1 +%% @see function_type/2 + +-spec function_type_return(syntaxTree()) -> syntaxTree(). + +function_type_return(Node) -> + case unwrap(Node) of + {type, _, 'fun', [_, Type]} -> + Type; + Node1 -> + (data(Node1))#function_type.return + end. + + +%% ===================================================================== +%% @doc Creates an abstract (subtype) constraint. The result represents +%% "Name :: Type". +%% +%% @see constraint_argument/1 +%% @see constraint_body/1 + +-record(constraint, {name :: syntaxTree(), + type :: syntaxTree()}). + +%% type(Node) = constraint +%% data(Node) = #constraint{name :: Name, +%% type :: Type} +%% +%% Name = syntaxTree() +%% Type = syntaxTree() +%% +%% `erl_parse' representation: +%% +%% {type, Pos, constraint, [{atom, Pos, is_subtype}, Name, Type]} +%% +%% Name = erl_parse() +%% Type = erl_parse() + +-spec constraint(syntaxTree(), syntaxTree()) -> syntaxTree(). + +constraint(Name, Type) -> + tree(constraint, + #constraint{name = Name, type = Type}). + +revert_constraint(Node) -> + Pos = get_pos(Node), + Name = constraint_argument(Node), + Type = constraint_body(Node), + {type, Pos, constraint, [Name, Type]}. + + +%% ===================================================================== +%% @doc Returns the name subtree of a `constraint' node. +%% +%% @see constraint/2 + +-spec constraint_argument(syntaxTree()) -> syntaxTree(). + +constraint_argument(Node) -> + case unwrap(Node) of + {type, _, constraint, [Name, _]} -> + Name; + Node1 -> + (data(Node1))#constraint.name + end. + +%% ===================================================================== +%% @doc Returns the type subtree of a `constraint' node. +%% +%% @see constraint/2 + +-spec constraint_body(syntaxTree()) -> syntaxTree(). + +constraint_body(Node) -> + case unwrap(Node) of + {type, _, constraint, [_, Type]} -> + Type; + Node1 -> + (data(Node1))#constraint.type + end. + + +%% ===================================================================== +%% @doc Creates an abstract type map assoc field. The result represents +%% "KeyType => ValueType". +%% +%% @see map_type_pair_key/1 +%% @see map_type_pair_value/1 + +-record(map_type_pair, {key :: syntaxTree(), + value :: syntaxTree()}). + +%% type(Node) = map_type_pair +%% data(Node) = #map_type_pair{key :: KeyType, +%% value :: ValueType} +%% +%% KeyType = syntaxTree() +%% ValueType = syntaxTree() +%% +%% `erl_parse' representation: +%% +%% {type, Pos, map_field_assoc, [KeyType, ValueType]} +%% +%% KeyType = erl_parse() +%% ValueType = erl_parse() + +-spec map_type_pair(syntaxTree(), syntaxTree()) -> syntaxTree(). + +map_type_pair(KeyType, ValueType) -> + tree(map_type_pair, + #map_type_pair{key = KeyType, value = ValueType}). + +revert_map_type_pair(Node) -> + Pos = get_pos(Node), + KeyType = map_type_pair_key(Node), + ValueType = map_type_pair_value(Node), + {type, Pos, map_field_assoc, [KeyType, ValueType]}. + +%% ===================================================================== +%% @doc Returns the key type subtrees of a `map_type_pair' node. +%% +%% @see map_type_pair/2 + +-spec map_type_pair_key(syntaxTree()) -> syntaxTree(). + +map_type_pair_key(Node) -> + case unwrap(Node) of + {type, _, map_field_assoc, [KeyType, _]} -> + KeyType; + Node1 -> + (data(Node1))#map_type_pair.key + end. + +%% ===================================================================== +%% @doc Returns the value type subtrees of a `map_type_pair' node. +%% +%% @see map_type_pair/2 + +-spec map_type_pair_value(syntaxTree()) -> syntaxTree(). + +map_type_pair_value(Node) -> + case unwrap(Node) of + {type, _, map_field_assoc, [_, ValueType]} -> + ValueType; + Node1 -> + (data(Node1))#map_type_pair.value + end. + + +%% ===================================================================== +%% @equiv map_type(any_size) + +map_type() -> + map_type(any_size). + +%% ===================================================================== +%% @doc Creates an abstract type map. If `Fields' is +%% `[F1, ..., Fn]', the result represents +%% "#{F1, ..., Fn}"; +%% otherwise, if `Fields' is `any_size', it represents +%% "map()". +%% +%% @see map_type_fields/1 + +%% type(Node) = map_type +%% data(Node) = Fields +%% +%% Fields = any_size | [syntaxTree()] +%% +%% `erl_parse' representation: +%% +%% {type, Pos, map, [Field]} +%% {type, Pos, map, any} +%% +%% Field = erl_parse() + +-spec map_type('any_size' | [syntaxTree()]) -> syntaxTree(). + +map_type(Fields) -> + tree(map_type, Fields). + +revert_map_type(Node) -> + Pos = get_pos(Node), + {type, Pos, map, map_type_fields(Node)}. + + +%% ===================================================================== +%% @doc Returns the list of field subtrees of a `map_type' node. +%% If `Node' represents "map()", `any_size' is returned; +%% otherwise, if `Node' represents +%% "#{F1, ..., Fn}", +%% `[F1, ..., Fn]' is returned. +%% +%% @see map_type/0 +%% @see map_type/1 + +-spec map_type_fields(syntaxTree()) -> 'any_size' | [syntaxTree()]. + +map_type_fields(Node) -> + case unwrap(Node) of + {type, _, map, Fields} when is_list(Fields) -> + Fields; + {type, _, map, any} -> + any_size; + Node1 -> + data(Node1) + end. + + +%% ===================================================================== +%% @doc Creates an abstract range type. The result represents +%% "Low .. High". +%% +%% @see integer_range_type_low/1 +%% @see integer_range_type_high/1 + +-record(integer_range_type, {low :: syntaxTree(), + high :: syntaxTree()}). + +%% type(Node) = integer_range_type +%% data(Node) = #integer_range_type{low :: Low, high :: High} +%% +%% Low = syntaxTree() +%% High = syntaxTree() +%% +%% `erl_parse' representation: +%% +%% {type, Pos, range, [Low, High]} +%% +%% Low = erl_parse() +%% High = erl_parse() + +-spec integer_range_type(syntaxTree(), syntaxTree()) -> syntaxTree(). + +integer_range_type(Low, High) -> + tree(integer_range_type, #integer_range_type{low = Low, high = High}). + +revert_integer_range_type(Node) -> + Pos = get_pos(Node), + Low = integer_range_type_low(Node), + High = integer_range_type_high(Node), + {type, Pos, range, [Low, High]}. + + +%% ===================================================================== +%% @doc Returns the low limit of an `integer_range_type' node. +%% +%% @see integer_range_type/2 + +-spec integer_range_type_low(syntaxTree()) -> syntaxTree(). + +integer_range_type_low(Node) -> + case unwrap(Node) of + {type, _, range, [Low, _]} -> + Low; + Node1 -> + (data(Node1))#integer_range_type.low + end. + +%% ===================================================================== +%% @doc Returns the high limit of an `integer_range_type' node. +%% +%% @see integer_range_type/2 + +-spec integer_range_type_high(syntaxTree()) -> syntaxTree(). + +integer_range_type_high(Node) -> + case unwrap(Node) of + {type, _, range, [_, High]} -> + High; + Node1 -> + (data(Node1))#integer_range_type.high + end. + + +%% ===================================================================== +%% @doc Creates an abstract record type. If `Fields' is +%% `[F1, ..., Fn]', the result represents +%% "#Name{F1, ..., Fn}". +%% +%% @see record_type_name/1 +%% @see record_type_fields/1 + +-record(record_type, {name :: syntaxTree(), + fields :: [syntaxTree()]}). + +%% type(Node) = record_type +%% data(Node) = #record_type{name = Name, fields = Fields} +%% +%% Name = syntaxTree() +%% Fields = [syntaxTree()] +%% +%% `erl_parse' representation: +%% +%% {type, Pos, record, [Name|Fields]} +%% +%% Name = erl_parse() +%% Fields = [erl_parse()] + +-spec record_type(syntaxTree(), [syntaxTree()]) -> syntaxTree(). + +record_type(Name, Fields) -> + tree(record_type, #record_type{name = Name, fields = Fields}). + +revert_record_type(Node) -> + Pos = get_pos(Node), + Name = record_type_name(Node), + Fields = record_type_fields(Node), + {type, Pos, record, [Name | Fields]}. + + +%% ===================================================================== +%% @doc Returns the name subtree of a `record_type' node. +%% +%% @see record_type/2 + +-spec record_type_name(syntaxTree()) -> syntaxTree(). + +record_type_name(Node) -> + case unwrap(Node) of + {type, _, record, [Name|_]} -> + Name; + Node1 -> + (data(Node1))#record_type.name + end. + +%% ===================================================================== +%% @doc Returns the fields subtree of a `record_type' node. +%% +%% @see record_type/2 + +-spec record_type_fields(syntaxTree()) -> [syntaxTree()]. + +record_type_fields(Node) -> + case unwrap(Node) of + {type, _, record, [_|Fields]} -> + Fields; + Node1 -> + (data(Node1))#record_type.fields + end. + + +%% ===================================================================== +%% @doc Creates an abstract record type field. The result represents +%% "Name :: Type". +%% +%% @see record_type_field_name/1 +%% @see record_type_field_type/1 + +-record(record_type_field, {name :: syntaxTree(), + type :: syntaxTree()}). + +%% type(Node) = record_type_field +%% data(Node) = #record_type_field{name = Name, type = Type} +%% +%% Name = syntaxTree() +%% Type = syntaxTree() +%% +%% `erl_parse' representation: +%% +%% {type, Pos, field_type, [Name, Type]} +%% +%% Name = erl_parse() +%% Type = erl_parse() + +-spec record_type_field(syntaxTree(), syntaxTree()) -> syntaxTree(). + +record_type_field(Name, Type) -> + tree(record_type_field, #record_type_field{name = Name, type = Type}). + +revert_record_type_field(Node) -> + Pos = get_pos(Node), + Name = record_type_field_name(Node), + Type = record_type_field_type(Node), + {type, Pos, field_type, [Name, Type]}. + + +%% ===================================================================== +%% @doc Returns the name subtree of a `record_type_field' node. +%% +%% @see record_type_field/2 + +-spec record_type_field_name(syntaxTree()) -> syntaxTree(). + +record_type_field_name(Node) -> + case unwrap(Node) of + {type, _, field_type, [Name, _]} -> + Name; + Node1 -> + (data(Node1))#record_type_field.name + end. + +%% ===================================================================== +%% @doc Returns the type subtree of a `record_type_field' node. +%% +%% @see record_type_field/2 + +-spec record_type_field_type(syntaxTree()) -> syntaxTree(). + +record_type_field_type(Node) -> + case unwrap(Node) of + {type, _, field_type, [_, Type]} -> + Type; + Node1 -> + (data(Node1))#record_type_field.type + end. + + +%% ===================================================================== +%% @equiv tuple_type(any_size) + +tuple_type() -> + tuple_type(any_size). + +%% ===================================================================== +%% @doc Creates an abstract type tuple. If `Elements' is +%% `[T1, ..., Tn]', the result represents +%% "{T1, ..., Tn}"; +%% otherwise, if `Elements' is `any_size', it represents +%% "tuple()". +%% +%% @see tuple_type_elements/1 + +%% type(Node) = tuple_type +%% data(Node) = Elements +%% +%% Elements = any_size | [syntaxTree()] +%% +%% `erl_parse' representation: +%% +%% {type, Pos, tuple, [Element]} +%% {type, Pos, tuple, any} +%% +%% Element = erl_parse() + +-spec tuple_type(any_size | [syntaxTree()]) -> syntaxTree(). + +tuple_type(Elements) -> + tree(tuple_type, Elements). + +revert_tuple_type(Node) -> + Pos = get_pos(Node), + {type, Pos, tuple, tuple_type_elements(Node)}. + + +%% ===================================================================== +%% @doc Returns the list of type element subtrees of a `tuple_type' node. +%% If `Node' represents "tuple()", `any_size' is returned; +%% otherwise, if `Node' represents +%% "{T1, ..., Tn}", +%% `[T1, ..., Tn]' is returned. +%% +%% @see tuple_type/0 +%% @see tuple_type/1 + +-spec tuple_type_elements(syntaxTree()) -> 'any_size' | [syntaxTree()]. + +tuple_type_elements(Node) -> + case unwrap(Node) of + {type, _, tuple, Elements} when is_list(Elements) -> + Elements; + {type, _, tuple, any} -> + any_size; + Node1 -> + data(Node1) + end. + + +%% ===================================================================== +%% @doc Creates an abstract type union. If `Types' is +%% `[T1, ..., Tn]', the result represents +%% "T1 | ... | Tn". +%% +%% @see type_union_types/1 + +%% type(Node) = type_union +%% data(Node) = Types +%% +%% Types = [syntaxTree()] +%% +%% `erl_parse' representation: +%% +%% {type, Pos, union, Elements} +%% +%% Elements = [erl_parse()] + +-spec type_union([syntaxTree()]) -> syntaxTree(). + +type_union(Types) -> + tree(type_union, Types). + +revert_type_union(Node) -> + Pos = get_pos(Node), + {type, Pos, union, type_union_types(Node)}. + + +%% ===================================================================== +%% @doc Returns the list of type subtrees of a `type_union' node. +%% +%% @see type_union/1 + +-spec type_union_types(syntaxTree()) -> [syntaxTree()]. + +type_union_types(Node) -> + case unwrap(Node) of + {type, _, union, Types} when is_list(Types) -> + Types; + Node1 -> + data(Node1) + end. + + +%% ===================================================================== +%% @doc Creates an abstract user type. If `Arguments' is +%% `[T1, ..., Tn]', the result represents +%% "TypeName(T1, ...Tn)". +%% +%% @see type_application/2 +%% @see user_type_application_name/1 +%% @see user_type_application_arguments/1 + +-record(user_type_application, {type_name :: syntaxTree(), + arguments :: [syntaxTree()]}). + +%% type(Node) = user_type_application +%% data(Node) = #user_type_application{type_name :: TypeName, +%% arguments :: Arguments} +%% +%% TypeName = syntaxTree() +%% Arguments = [syntaxTree()] +%% +%% `erl_parse' representation: +%% +%% {user_type, Pos, Name, Arguments} +%% +%% Name = erl_parse() +%% Arguments = [Type] +%% Type = erl_parse() + +-spec user_type_application(syntaxTree(), [syntaxTree()]) -> syntaxTree(). + +user_type_application(TypeName, Arguments) -> + tree(user_type_application, + #user_type_application{type_name = TypeName, arguments = Arguments}). + +revert_user_type_application(Node) -> + Pos = get_pos(Node), + TypeName = user_type_application_name(Node), + Arguments = user_type_application_arguments(Node), + {user_type, Pos, atom_value(TypeName), Arguments}. + + +%% ===================================================================== +%% @doc Returns the type name subtree of a `user_type_application' node. +%% +%% @see user_type_application/2 + +-spec user_type_application_name(syntaxTree()) -> syntaxTree(). + +user_type_application_name(Node) -> + case unwrap(Node) of + {user_type, Pos, Name, _} -> + set_pos(atom(Name), Pos); + Node1 -> + (data(Node1))#user_type_application.type_name + end. + + +%% ===================================================================== +%% @doc Returns the arguments subtrees of a `user_type_application' node. +%% +%% @see user_type_application/2 + +-spec user_type_application_arguments(syntaxTree()) -> [syntaxTree()]. + +user_type_application_arguments(Node) -> + case unwrap(Node) of + {user_type, _, _, Arguments} -> + Arguments; + Node1 -> + (data(Node1))#user_type_application.arguments + end. + + +%% ===================================================================== +%% @doc Creates an abstract typed record field specification. The +%% result represents "Field :: Type". +%% +%% @see typed_record_field_body/1 +%% @see typed_record_field_type/1 + +-record(typed_record_field, {body :: syntaxTree(), + type :: syntaxTree()}). + +%% type(Node) = typed_record_field +%% data(Node) = #typed_record_field{body :: Field +%% type = Type} +%% +%% Field = syntaxTree() +%% Type = syntaxTree() + +-spec typed_record_field(syntaxTree(), syntaxTree()) -> syntaxTree(). + +typed_record_field(Field, Type) -> + tree(typed_record_field, + #typed_record_field{body = Field, type = Type}). + + +%% ===================================================================== +%% @doc Returns the field subtree of a `typed_record_field' node. +%% +%% @see typed_record_field/2 + +-spec typed_record_field_body(syntaxTree()) -> syntaxTree(). + +typed_record_field_body(Node) -> + (data(Node))#typed_record_field.body. + + +%% ===================================================================== +%% @doc Returns the type subtree of a `typed_record_field' node. +%% +%% @see typed_record_field/2 + +-spec typed_record_field_type(syntaxTree()) -> syntaxTree(). + +typed_record_field_type(Node) -> + (data(Node))#typed_record_field.type. + + +%% ===================================================================== +%% @doc Creates an abstract list comprehension. If `Body' is +%% `[E1, ..., En]', the result represents +%% "[Template || E1, ..., En]". +%% +%% @see list_comp_template/1 +%% @see list_comp_body/1 +%% @see generator/2 + +-record(list_comp, {template :: syntaxTree(), body :: [syntaxTree()]}). + +%% type(Node) = list_comp +%% data(Node) = #list_comp{template :: Template, body :: Body} +%% +%% Template = Node = syntaxTree() +%% Body = [syntaxTree()] +%% +%% `erl_parse' representation: +%% +%% {lc, Pos, Template, Body} +%% +%% Template = erl_parse() +%% Body = [erl_parse()] \ [] + +-spec list_comp(syntaxTree(), [syntaxTree()]) -> syntaxTree(). + +list_comp(Template, Body) -> + tree(list_comp, #list_comp{template = Template, body = Body}). + +revert_list_comp(Node) -> + Pos = get_pos(Node), + Template = list_comp_template(Node), + Body = list_comp_body(Node), + {lc, Pos, Template, Body}. + + +%% ===================================================================== +%% @doc Returns the template subtree of a `list_comp' node. +%% +%% @see list_comp/2 + +-spec list_comp_template(syntaxTree()) -> syntaxTree(). + +list_comp_template(Node) -> + case unwrap(Node) of + {lc, _, Template, _} -> + Template; + Node1 -> + (data(Node1))#list_comp.template + end. + + +%% ===================================================================== +%% @doc Returns the list of body subtrees of a `list_comp' node. +%% +%% @see list_comp/2 + +-spec list_comp_body(syntaxTree()) -> [syntaxTree()]. + +list_comp_body(Node) -> + case unwrap(Node) of + {lc, _, _, Body} -> + Body; + Node1 -> + (data(Node1))#list_comp.body + end. + +%% ===================================================================== +%% @doc Creates an abstract binary comprehension. If `Body' is +%% `[E1, ..., En]', the result represents +%% "<<Template || E1, ..., En>>". +%% +%% @see binary_comp_template/1 +%% @see binary_comp_body/1 +%% @see generator/2 + +-record(binary_comp, {template :: syntaxTree(), body :: [syntaxTree()]}). + +%% type(Node) = binary_comp +%% data(Node) = #binary_comp{template :: Template, body :: Body} +%% +%% Template = Node = syntaxTree() +%% Body = [syntaxTree()] +%% +%% `erl_parse' representation: +%% +%% {bc, Pos, Template, Body} +%% +%% Template = erl_parse() +%% Body = [erl_parse()] \ [] + +-spec binary_comp(syntaxTree(), [syntaxTree()]) -> syntaxTree(). + +binary_comp(Template, Body) -> + tree(binary_comp, #binary_comp{template = Template, body = Body}). + +revert_binary_comp(Node) -> + Pos = get_pos(Node), + Template = binary_comp_template(Node), + Body = binary_comp_body(Node), + {bc, Pos, Template, Body}. + + +%% ===================================================================== +%% @doc Returns the template subtree of a `binary_comp' node. +%% +%% @see binary_comp/2 + +-spec binary_comp_template(syntaxTree()) -> syntaxTree(). + +binary_comp_template(Node) -> + case unwrap(Node) of + {bc, _, Template, _} -> + Template; + Node1 -> + (data(Node1))#binary_comp.template + end. + + +%% ===================================================================== +%% @doc Returns the list of body subtrees of a `binary_comp' node. +%% +%% @see binary_comp/2 + +-spec binary_comp_body(syntaxTree()) -> [syntaxTree()]. + +binary_comp_body(Node) -> + case unwrap(Node) of + {bc, _, _, Body} -> + Body; + Node1 -> + (data(Node1))#binary_comp.body + end. + + +%% ===================================================================== +%% @doc Creates an abstract generator. The result represents +%% "Pattern <- Body". +%% +%% @see generator_pattern/1 +%% @see generator_body/1 +%% @see list_comp/2 +%% @see binary_comp/2 + +-record(generator, {pattern :: syntaxTree(), body :: syntaxTree()}). + +%% type(Node) = generator +%% data(Node) = #generator{pattern :: Pattern, body :: Body} +%% +%% Pattern = Argument = syntaxTree() +%% +%% `erl_parse' representation: +%% +%% {generate, Pos, Pattern, Body} +%% +%% Pattern = Body = erl_parse() + +-spec generator(syntaxTree(), syntaxTree()) -> syntaxTree(). + +generator(Pattern, Body) -> + tree(generator, #generator{pattern = Pattern, body = Body}). + +revert_generator(Node) -> + Pos = get_pos(Node), + Pattern = generator_pattern(Node), + Body = generator_body(Node), + {generate, Pos, Pattern, Body}. + + +%% ===================================================================== +%% @doc Returns the pattern subtree of a `generator' node. +%% +%% @see generator/2 + +-spec generator_pattern(syntaxTree()) -> syntaxTree(). + +generator_pattern(Node) -> + case unwrap(Node) of + {generate, _, Pattern, _} -> + Pattern; + Node1 -> + (data(Node1))#generator.pattern + end. + + +%% ===================================================================== +%% @doc Returns the body subtree of a `generator' node. +%% +%% @see generator/2 + +-spec generator_body(syntaxTree()) -> syntaxTree(). + +generator_body(Node) -> + case unwrap(Node) of + {generate, _, _, Body} -> + Body; + Node1 -> + (data(Node1))#generator.body + end. + + +%% ===================================================================== +%% @doc Creates an abstract binary_generator. The result represents +%% "Pattern <- Body". +%% +%% @see binary_generator_pattern/1 +%% @see binary_generator_body/1 +%% @see list_comp/2 +%% @see binary_comp/2 + +-record(binary_generator, {pattern :: syntaxTree(), body :: syntaxTree()}). + +%% type(Node) = binary_generator +%% data(Node) = #binary_generator{pattern :: Pattern, body :: Body} +%% +%% Pattern = Argument = syntaxTree() +%% +%% `erl_parse' representation: +%% +%% {b_generate, Pos, Pattern, Body} +%% +%% Pattern = Body = erl_parse() + +-spec binary_generator(syntaxTree(), syntaxTree()) -> syntaxTree(). + +binary_generator(Pattern, Body) -> + tree(binary_generator, #binary_generator{pattern = Pattern, body = Body}). + +revert_binary_generator(Node) -> + Pos = get_pos(Node), + Pattern = binary_generator_pattern(Node), + Body = binary_generator_body(Node), + {b_generate, Pos, Pattern, Body}. + + +%% ===================================================================== +%% @doc Returns the pattern subtree of a `generator' node. +%% +%% @see binary_generator/2 + +-spec binary_generator_pattern(syntaxTree()) -> syntaxTree(). + +binary_generator_pattern(Node) -> + case unwrap(Node) of + {b_generate, _, Pattern, _} -> + Pattern; + Node1 -> + (data(Node1))#binary_generator.pattern + end. + + +%% ===================================================================== +%% @doc Returns the body subtree of a `generator' node. +%% +%% @see binary_generator/2 + +-spec binary_generator_body(syntaxTree()) -> syntaxTree(). + +binary_generator_body(Node) -> + case unwrap(Node) of + {b_generate, _, _, Body} -> + Body; + Node1 -> + (data(Node1))#binary_generator.body + end. + + +%% ===================================================================== +%% @doc Creates an abstract block expression. If `Body' is +%% `[B1, ..., Bn]', the result represents "begin +%% B1, ..., Bn end". +%% +%% @see block_expr_body/1 + +%% type(Node) = block_expr +%% data(Node) = Body +%% +%% Body = [syntaxTree()] +%% +%% `erl_parse' representation: +%% +%% {block, Pos, Body} +%% +%% Body = [erl_parse()] \ [] + +-spec block_expr([syntaxTree()]) -> syntaxTree(). + +block_expr(Body) -> + tree(block_expr, Body). + +revert_block_expr(Node) -> + Pos = get_pos(Node), + Body = block_expr_body(Node), + {block, Pos, Body}. + + +%% ===================================================================== +%% @doc Returns the list of body subtrees of a `block_expr' node. +%% +%% @see block_expr/1 + +-spec block_expr_body(syntaxTree()) -> [syntaxTree()]. + +block_expr_body(Node) -> + case unwrap(Node) of + {block, _, Body} -> + Body; + Node1 -> + data(Node1) end. @@ -6168,6 +7355,8 @@ revert(Node) -> revert_root(Node) -> case type(Node) of + annotated_type -> + revert_annotated_type(Node); application -> revert_application(Node); atom -> @@ -6182,6 +7371,8 @@ revert_root(Node) -> revert_binary_field(Node); binary_generator -> revert_binary_generator(Node); + bitstring_type -> + revert_bitstring_type(Node); block_expr -> revert_block_expr(Node); case_expr -> @@ -6194,6 +7385,10 @@ revert_root(Node) -> revert_clause(Node); cond_expr -> revert_cond_expr(Node); + constrained_function_type -> + revert_constrained_function_type(Node); + constraint -> + revert_constraint(Node); eof_marker -> revert_eof_marker(Node); error_marker -> @@ -6202,8 +7397,12 @@ revert_root(Node) -> revert_float(Node); fun_expr -> revert_fun_expr(Node); + fun_type -> + revert_fun_type(Node); function -> revert_function(Node); + function_type -> + revert_function_type(Node); generator -> revert_generator(Node); if_expr -> @@ -6214,6 +7413,8 @@ revert_root(Node) -> revert_infix_expr(Node); integer -> revert_integer(Node); + integer_range_type -> + revert_integer_range_type(Node); list -> revert_list(Node); list_comp -> @@ -6224,6 +7425,10 @@ revert_root(Node) -> revert_map_field_assoc(Node); map_field_exact -> revert_map_field_exact(Node); + map_type -> + revert_map_type(Node); + map_type_pair -> + revert_map_type_pair(Node); match_expr -> revert_match_expr(Node); module_qualifier -> @@ -6244,14 +7449,26 @@ revert_root(Node) -> revert_record_expr(Node); record_index_expr -> revert_record_index_expr(Node); + record_type -> + revert_record_type(Node); + record_type_field -> + revert_record_type_field(Node); + type_application -> + revert_type_application(Node); + type_union -> + revert_type_union(Node); string -> revert_string(Node); try_expr -> revert_try_expr(Node); tuple -> revert_tuple(Node); + tuple_type -> + revert_tuple_type(Node); underscore -> revert_underscore(Node); + user_type_application -> + revert_user_type_application(Node); variable -> revert_variable(Node); warning_marker -> @@ -6379,6 +7596,9 @@ subtrees(T) -> []; false -> case type(T) of + annotated_type -> + [[annotated_type_name(T)], + [annotated_type_body(T)]]; application -> [[application_operator(T)], application_arguments(T)]; @@ -6407,6 +7627,9 @@ subtrees(T) -> binary_generator -> [[binary_generator_pattern(T)], [binary_generator_body(T)]]; + bitstring_type -> + [[bitstring_type_m(T)], + [bitstring_type_n(T)]]; block_expr -> [block_expr_body(T)]; case_expr -> @@ -6429,14 +7652,30 @@ subtrees(T) -> [cond_expr_clauses(T)]; conjunction -> [conjunction_body(T)]; + constrained_function_type -> + C = constrained_function_type_argument(T), + [[constrained_function_type_body(T)], + conjunction_body(C)]; + constraint -> + [[constraint_argument(T)], + constraint_body(T)]; disjunction -> [disjunction_body(T)]; form_list -> [form_list_elements(T)]; fun_expr -> [fun_expr_clauses(T)]; + fun_type -> + []; function -> [[function_name(T)], function_clauses(T)]; + function_type -> + case function_type_arguments(T) of + any_arity -> + [[function_type_return(T)]]; + As -> + [As,[function_type_return(T)]] + end; generator -> [[generator_pattern(T)], [generator_body(T)]]; if_expr -> @@ -6447,6 +7686,9 @@ subtrees(T) -> [[infix_expr_left(T)], [infix_expr_operator(T)], [infix_expr_right(T)]]; + integer_range_type -> + [[integer_range_type_low(T)], + [integer_range_type_high(T)]]; list -> case list_suffix(T) of none -> @@ -6476,6 +7718,11 @@ subtrees(T) -> map_field_exact -> [[map_field_exact_name(T)], [map_field_exact_value(T)]]; + map_type -> + [map_type_fields(T)]; + map_type_pair -> + [[map_type_pair_key(T)], + [map_type_pair_value(T)]]; match_expr -> [[match_expr_pattern(T)], [match_expr_body(T)]]; @@ -6523,6 +7770,12 @@ subtrees(T) -> record_index_expr -> [[record_index_expr_type(T)], [record_index_expr_field(T)]]; + record_type -> + [[record_type_name(T)], + record_type_fields(T)]; + record_type_field -> + [[record_type_field_name(T)], + [record_type_field_type(T)]]; size_qualifier -> [[size_qualifier_body(T)], [size_qualifier_argument(T)]]; @@ -6532,7 +7785,20 @@ subtrees(T) -> try_expr_handlers(T), try_expr_after(T)]; tuple -> - [tuple_elements(T)] + [tuple_elements(T)]; + tuple_type -> + [tuple_type_elements(T)]; + type_application -> + [[type_application_name(T)], + type_application_arguments(T)]; + type_union -> + [type_union_types(T)]; + typed_record_field -> + [[typed_record_field_body(T)], + [typed_record_field_type(T)]]; + user_type_application -> + [[user_type_application_name(T)], + user_type_application_arguments(T)] end end. @@ -6576,6 +7842,7 @@ update_tree(Node, Groups) -> -spec make_tree(atom(), [[syntaxTree()]]) -> syntaxTree(). +make_tree(annotated_type, [[N], [T]]) -> annotated_type(N, T); make_tree(application, [[F], A]) -> application(F, A); make_tree(arity_qualifier, [[N], [A]]) -> arity_qualifier(N, A); make_tree(attribute, [[N]]) -> attribute(N); @@ -6585,6 +7852,7 @@ make_tree(binary_comp, [[T], B]) -> binary_comp(T, B); make_tree(binary_field, [[B]]) -> binary_field(B); make_tree(binary_field, [[B], Ts]) -> binary_field(B, Ts); make_tree(binary_generator, [[P], [E]]) -> binary_generator(P, E); +make_tree(bitstring_type, [[M], [N]]) -> bitstring_type(M, N); make_tree(block_expr, [B]) -> block_expr(B); make_tree(case_expr, [[A], C]) -> case_expr(A, C); make_tree(catch_expr, [[B]]) -> catch_expr(B); @@ -6593,14 +7861,20 @@ make_tree(clause, [P, B]) -> clause(P, none, B); make_tree(clause, [P, [G], B]) -> clause(P, G, B); make_tree(cond_expr, [C]) -> cond_expr(C); make_tree(conjunction, [E]) -> conjunction(E); +make_tree(constrained_function_type, [[F],C]) -> + constrained_function_type(F, C); +make_tree(constraint, [[N], Ts]) -> constraint(N, Ts); make_tree(disjunction, [E]) -> disjunction(E); make_tree(form_list, [E]) -> form_list(E); make_tree(fun_expr, [C]) -> fun_expr(C); make_tree(function, [[N], C]) -> function(N, C); +make_tree(function_type, [[T]]) -> function_type(T); +make_tree(function_type, [A,[T]]) -> function_type(A, T); make_tree(generator, [[P], [E]]) -> generator(P, E); make_tree(if_expr, [C]) -> if_expr(C); make_tree(implicit_fun, [[N]]) -> implicit_fun(N); make_tree(infix_expr, [[L], [F], [R]]) -> infix_expr(L, F, R); +make_tree(integer_range_type, [[L],[H]]) -> integer_range_type(L, H); make_tree(list, [P]) -> list(P); make_tree(list, [P, [S]]) -> list(P, S); make_tree(list_comp, [[T], B]) -> list_comp(T, B); @@ -6610,6 +7884,8 @@ make_tree(map_expr, [Fs]) -> map_expr(Fs); make_tree(map_expr, [[E], Fs]) -> map_expr(E, Fs); make_tree(map_field_assoc, [[K], [V]]) -> map_field_assoc(K, V); make_tree(map_field_exact, [[K], [V]]) -> map_field_exact(K, V); +make_tree(map_type, [Fs]) -> map_type(Fs); +make_tree(map_type_pair, [[K],[V]]) -> map_type_pair(K, V); make_tree(match_expr, [[P], [E]]) -> match_expr(P, E); make_tree(named_fun_expr, [[N], C]) -> named_fun_expr(N, C); make_tree(module_qualifier, [[M], [N]]) -> module_qualifier(M, N); @@ -6625,9 +7901,16 @@ make_tree(record_field, [[N]]) -> record_field(N); make_tree(record_field, [[N], [E]]) -> record_field(N, E); make_tree(record_index_expr, [[T], [F]]) -> record_index_expr(T, F); +make_tree(record_type, [[N],Fs]) -> record_type(N, Fs); +make_tree(record_type_field, [[N],[T]]) -> record_type_field(N, T); make_tree(size_qualifier, [[N], [A]]) -> size_qualifier(N, A); make_tree(try_expr, [B, C, H, A]) -> try_expr(B, C, H, A); -make_tree(tuple, [E]) -> tuple(E). +make_tree(tuple, [E]) -> tuple(E); +make_tree(tuple_type, [Es]) -> tuple_type(Es); +make_tree(type_application, [[N], Ts]) -> type_application(N, Ts); +make_tree(type_union, [Es]) -> type_union(Es); +make_tree(typed_record_field, [[F],[T]]) -> typed_record_field(F, T); +make_tree(user_type_application, [[N], Ts]) -> user_type_application(N, Ts). %% ===================================================================== @@ -6954,6 +8237,7 @@ fold_variable_names(Vs) -> unfold_variable_names(Vs, Pos) -> [set_pos(variable(V), Pos) || V <- Vs]. + %% Support functions for transforming lists of record field definitions. %% %% There is no unique representation for field definitions in the @@ -6968,6 +8252,16 @@ fold_record_fields(Fs) -> [fold_record_field(F) || F <- Fs]. fold_record_field(F) -> + case type(F) of + typed_record_field -> + Field = fold_record_field_1(typed_record_field_body(F)), + Type = typed_record_field_type(F), + {typed_record_field, Field, Type}; + record_field -> + fold_record_field_1(F) + end. + +fold_record_field_1(F) -> Pos = get_pos(F), Name = record_field_name(F), case record_field_value(F) of @@ -6980,10 +8274,11 @@ fold_record_field(F) -> unfold_record_fields(Fs) -> [unfold_record_field(F) || F <- Fs]. -unfold_record_field({typed_record_field, Field, _Type}) -> - unfold_record_field_1(Field); +unfold_record_field({typed_record_field, Field, Type}) -> + F = unfold_record_field_1(Field), + set_pos(typed_record_field(F, Type), get_pos(F)); unfold_record_field(Field) -> - unfold_record_field_1(Field). + unfold_record_field_1(Field). unfold_record_field_1({record_field, Pos, Name}) -> set_pos(record_field(Name), Pos); @@ -7010,5 +8305,4 @@ unfold_binary_field_type({Type, Size}, Pos) -> unfold_binary_field_type(Type, Pos) -> set_pos(atom(Type), Pos). - %% ===================================================================== diff --git a/lib/syntax_tools/src/erl_syntax_lib.erl b/lib/syntax_tools/src/erl_syntax_lib.erl index 58c4cc5244..9815559779 100644 --- a/lib/syntax_tools/src/erl_syntax_lib.erl +++ b/lib/syntax_tools/src/erl_syntax_lib.erl @@ -36,6 +36,7 @@ analyze_import_attribute/1, analyze_module_attribute/1, analyze_record_attribute/1, analyze_record_expr/1, analyze_record_field/1, analyze_wild_attribute/1, annotate_bindings/1, + analyze_type_application/1, analyze_type_name/1, annotate_bindings/2, fold/3, fold_subtrees/3, foldl_listlist/3, function_name_expansions/1, is_fail_expr/1, limit/2, limit/3, map/2, map_subtrees/2, mapfold/3, mapfold_subtrees/3, @@ -1029,14 +1030,17 @@ is_fail_expr(E) -> %%
    `{records, Records}'
    %%
      %%
    • `Records = [{atom(), Fields}]'
    • -%%
    • `Fields = [{atom(), Default}]'
    • +%%
    • `Fields = [{atom(), {Default, Type}}]'
    • %%
    • `Default = none | syntaxTree()'
    • +%%
    • `Type = none | syntaxTree()'
    • %%
    %% `Records' is a list of pairs representing the names %% and corresponding field declarations of all record declaration %% attributes occurring in `Forms'. For fields declared %% without a default value, the corresponding value for -%% `Default' is the atom `none' (cf. +%% `Default' is the atom `none'. Similarly, for fields declared +%% without a type, the corresponding value for `Type' is the +%% atom `none' (cf. %% `analyze_record_attribute/1'). We do not guarantee %% that each record name occurs at most once in the list. The %% order of listing is not defined.
    @@ -1055,9 +1059,9 @@ is_fail_expr(E) -> %% %% @see analyze_wild_attribute/1 %% @see analyze_export_attribute/1 +%% @see analyze_function/1 %% @see analyze_import_attribute/1 %% @see analyze_record_attribute/1 -%% @see analyze_function/1 %% @see erl_syntax:error_marker_info/1 %% @see erl_syntax:warning_marker_info/1 @@ -1102,8 +1106,6 @@ collect_attribute(file, _, Info) -> Info; collect_attribute(record, {R, L}, Info) -> finfo_add_record(R, L, Info); -collect_attribute(spec, _, Info) -> - Info; collect_attribute(_, {N, V}, Info) -> finfo_add_attribute(N, V, Info). @@ -1114,12 +1116,15 @@ collect_attribute(_, {N, V}, Info) -> module_imports = [] :: [atom()], imports = [] :: [{atom(), [{atom(), arity()}]}], attributes = [] :: [{atom(), term()}], - records = [] :: [{atom(), [{atom(), field_default()}]}], + records = [] :: [{atom(), [{atom(), + field_default(), + field_type()}]}], errors = [] :: [term()], warnings = [] :: [term()], functions = [] :: [{atom(), arity()}]}). -type field_default() :: 'none' | erl_syntax:syntaxTree(). +-type field_type() :: 'none' | erl_syntax:syntaxTree(). new_finfo() -> #forms{}. @@ -1326,8 +1331,6 @@ analyze_attribute(file, Node) -> analyze_file_attribute(Node); analyze_attribute(record, Node) -> analyze_record_attribute(Node); -analyze_attribute(spec, _Node) -> - spec; analyze_attribute(_, Node) -> %% A "wild" attribute (such as e.g. a `compile' directive). analyze_wild_attribute(Node). @@ -1522,6 +1525,55 @@ analyze_import_attribute(Node) -> end. +%% ===================================================================== +%% @spec analyze_type_name(Node::syntaxTree()) -> TypeName +%% +%% TypeName = atom() +%% | {atom(), integer()} +%% | {ModuleName, {atom(), integer()}} +%% ModuleName = atom() +%% +%% @doc Returns the type name represented by a syntax tree. If +%% `Node' represents a type name, such as +%% "`foo/1'" or "`bloggs:fred/2'", a uniform +%% representation of that name is returned. +%% +%% The evaluation throws `syntax_error' if +%% `Node' does not represent a well-formed type name. + +-spec analyze_type_name(erl_syntax:syntaxTree()) -> typeName(). + +analyze_type_name(Node) -> + case erl_syntax:type(Node) of + atom -> + erl_syntax:atom_value(Node); + arity_qualifier -> + A = erl_syntax:arity_qualifier_argument(Node), + N = erl_syntax:arity_qualifier_body(Node), + + case ((erl_syntax:type(A) =:= integer) + and (erl_syntax:type(N) =:= atom)) + of + true -> + append_arity(erl_syntax:integer_value(A), + erl_syntax:atom_value(N)); + _ -> + throw(syntax_error) + end; + module_qualifier -> + M = erl_syntax:module_qualifier_argument(Node), + case erl_syntax:type(M) of + atom -> + N = erl_syntax:module_qualifier_body(Node), + N1 = analyze_type_name(N), + {erl_syntax:atom_value(M), N1}; + _ -> + throw(syntax_error) + end; + _ -> + throw(syntax_error) + end. + %% ===================================================================== %% @spec analyze_wild_attribute(Node::syntaxTree()) -> {atom(), term()} %% @@ -1547,6 +1599,7 @@ analyze_wild_attribute(Node) -> atom -> case erl_syntax:attribute_arguments(Node) of [V] -> + %% Note: does not work well with macros. case catch {ok, erl_syntax:concrete(V)} of {ok, Val} -> {erl_syntax:atom_value(N), Val}; @@ -1568,17 +1621,22 @@ analyze_wild_attribute(Node) -> %% @spec analyze_record_attribute(Node::syntaxTree()) -> %% {atom(), Fields} %% -%% Fields = [{atom(), none | syntaxTree()}] +%% Fields = [{atom(), {Default, Type}}] +%% Default = none | syntaxTree() +%% Type = none | syntaxTree() %% %% @doc Returns the name and the list of fields of a record declaration %% attribute. The result is a pair `{Name, Fields}', if %% `Node' represents "`-record(Name, {...}).'", %% where `Fields' is a list of pairs `{Label, -%% Default}' for each field "`Label'" or "`Label = -%% Default'" in the declaration, listed in left-to-right +%% {Default, Type}}' for each field "`Label'", "`Label = +%% Default'", "`Label :: Type'", or +%% "`Label = Default :: Type'" in the declaration, +%% listed in left-to-right %% order. If the field has no default-value declaration, the value for -%% `Default' will be the atom `none'. We do not -%% guarantee that each label occurs at most one in the list. +%% `Default' will be the atom `none'. If the field has no type declaration, +%% the value for `Type' will be the atom `none'. We do not +%% guarantee that each label occurs at most once in the list. %% %% The evaluation throws `syntax_error' if %% `Node' does not represent a well-formed record declaration @@ -1587,7 +1645,9 @@ analyze_wild_attribute(Node) -> %% @see analyze_attribute/1 %% @see analyze_record_field/1 --type fields() :: [{atom(), 'none' | erl_syntax:syntaxTree()}]. +-type field() :: {atom(), {field_default(), field_type()}}. + +-type fields() :: [field()]. -spec analyze_record_attribute(erl_syntax:syntaxTree()) -> {atom(), fields()}. @@ -1625,7 +1685,7 @@ analyze_record_attribute_tuple(Node) -> %% {atom(), Info} | atom() %% %% Info = {atom(), [{atom(), Value}]} | {atom(), atom()} | atom() -%% Value = none | syntaxTree() +%% Value = syntaxTree() %% %% @doc Returns the record name and field name/names of a record %% expression. If `Node' has type `record_expr', @@ -1645,9 +1705,9 @@ analyze_record_attribute_tuple(Node) -> %% %% For a `record_expr' node, `Info' represents %% the record name and the list of descriptors for the involved fields, -%% listed in the order they appear. (See -%% `analyze_record_field/1' for details on the field -%% descriptors). For a `record_access' node, +%% listed in the order they appear. A field descriptor is a pair +%% `{Label, Value}', if `Node' represents "`Label = Value'". +%% For a `record_access' node, %% `Info' represents the record name and the field name. For a %% `record_index_expr' node, `Info' represents the %% record name and the name field name. @@ -1659,7 +1719,7 @@ analyze_record_attribute_tuple(Node) -> %% @see analyze_record_attribute/1 %% @see analyze_record_field/1 --type info() :: {atom(), [{atom(), 'none' | erl_syntax:syntaxTree()}]} +-type info() :: {atom(), [{atom(), erl_syntax:syntaxTree()}]} | {atom(), atom()} | atom(). -spec analyze_record_expr(erl_syntax:syntaxTree()) -> {atom(), info()} | atom(). @@ -1670,8 +1730,9 @@ analyze_record_expr(Node) -> A = erl_syntax:record_expr_type(Node), case erl_syntax:type(A) of atom -> - Fs = [analyze_record_field(F) - || F <- erl_syntax:record_expr_fields(Node)], + Fs0 = [analyze_record_field(F) + || F <- erl_syntax:record_expr_fields(Node)], + Fs = [{N, D} || {N, {D, _T}} <- Fs0], {record_expr, {erl_syntax:atom_value(A), Fs}}; _ -> throw(syntax_error) @@ -1713,16 +1774,19 @@ analyze_record_expr(Node) -> end. %% ===================================================================== -%% @spec analyze_record_field(Node::syntaxTree()) -> {atom(), Value} +%% @spec analyze_record_field(Node::syntaxTree()) -> {atom(), {Default, Type}} %% -%% Value = none | syntaxTree() +%% Default = none | syntaxTree() +%% Type = none | syntaxTree() %% -%% @doc Returns the label and value-expression of a record field -%% specifier. The result is a pair `{Label, Value}', if -%% `Node' represents "`Label = Value'" or -%% "`Label'", where in the first case, `Value' is -%% a syntax tree, and in the second case `Value' is -%% `none'. +%% @doc Returns the label, value-expression, and type of a record field +%% specifier. The result is a pair `{Label, {Default, Type}}', if +%% `Node' represents "`Label'", "`Label = Default'", +%% "`Label :: Type'", or +%% "`Label = Default :: Type'". +%% If the field has no value-expression, the value for +%% `Default' will be the atom `none'. If the field has no type, +%% the value for `Type' will be the atom `none'. %% %% The evaluation throws `syntax_error' if %% `Node' does not represent a well-formed record field @@ -1731,8 +1795,7 @@ analyze_record_expr(Node) -> %% @see analyze_record_attribute/1 %% @see analyze_record_expr/1 --spec analyze_record_field(erl_syntax:syntaxTree()) -> - {atom(), 'none' | erl_syntax:syntaxTree()}. +-spec analyze_record_field(erl_syntax:syntaxTree()) -> field(). analyze_record_field(Node) -> case erl_syntax:type(Node) of @@ -1741,10 +1804,15 @@ analyze_record_field(Node) -> case erl_syntax:type(A) of atom -> T = erl_syntax:record_field_value(Node), - {erl_syntax:atom_value(A), T}; + {erl_syntax:atom_value(A), {T, none}}; _ -> throw(syntax_error) end; + typed_record_field -> + F = erl_syntax:typed_record_field_body(Node), + {N, {V, _none}} = analyze_record_field(F), + T = erl_syntax:typed_record_field_type(Node), + {N, {V, T}}; _ -> throw(syntax_error) end. @@ -1886,6 +1954,55 @@ analyze_application(Node) -> end. +%% ===================================================================== +%% @spec analyze_type_application(Node::syntaxTree()) -> typeName() +%% +%% TypeName = {atom(), integer()} +%% | {ModuleName, {atom(), integer()}} +%% ModuleName = atom() +%% +%% @doc Returns the name of a used type. The result is a +%% representation of the name of the used pre-defined or local type `N/A', +%% if `Node' represents a local (user) type application +%% "`N(T_1, ..., T_A)'", or +%% a representation of the name of the used remote type `M:N/A' +%% if `Node' represents a remote user type application +%% "`M:N(T_1, ..., T_A)'". +%% +%% The evaluation throws `syntax_error' if `Node' does not represent a +%% well-formed (user) type application expression. +%% +%% @see analyze_type_name/1 + +-type typeName() :: atom() | {module(), atom(), arity()} | {atom(), arity()}. + +-spec analyze_type_application(erl_syntax:syntaxTree()) -> typeName(). + +analyze_type_application(Node) -> + case erl_syntax:type(Node) of + type_application -> + A = length(erl_syntax:type_application_arguments(Node)), + N = erl_syntax:type_application_name(Node), + case catch {ok, analyze_type_name(N)} of + {ok, TypeName} -> + append_arity(A, TypeName); + _ -> + throw(syntax_error) + end; + user_type_application -> + A = length(erl_syntax:user_type_application_arguments(Node)), + N = erl_syntax:user_type_application_name(Node), + case catch {ok, analyze_type_name(N)} of + {ok, TypeName} -> + append_arity(A, TypeName); + _ -> + throw(syntax_error) + end; + _ -> + throw(syntax_error) + end. + + %% ===================================================================== %% @spec function_name_expansions(Names::[Name]) -> [{ShortName, Name}] %% -- cgit v1.2.3 From 4e1c9526bc682e79422d908da0f2b79f3a9936d5 Mon Sep 17 00:00:00 2001 From: Hans Bolinder Date: Thu, 5 Nov 2015 13:26:14 +0100 Subject: syntax_tools: Update igor to handle typed records --- lib/syntax_tools/src/igor.erl | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/igor.erl b/lib/syntax_tools/src/igor.erl index 4557678f9d..1d14bd7c3a 100644 --- a/lib/syntax_tools/src/igor.erl +++ b/lib/syntax_tools/src/igor.erl @@ -2612,6 +2612,19 @@ get_module_info(Forms) -> fold_record_fields(Rs) -> [{N, [fold_record_field(F) || F <- Fs]} || {N, Fs} <- Rs]. +fold_record_field({_Name, {none, _Type}} = None) -> + None; +fold_record_field({Name, {F, Type}}) -> + case erl_syntax:is_literal(F) of + true -> + {Name, {value, erl_syntax:concrete(F)}, Type}; + false -> + %% The default value for the field is not a constant, so we + %% represent it by a hash value instead. (We don't want to + %% do this in the general case.) + {Name, {hash, erlang:phash(F, 16#ffffff)}, Type} + end; +%% The following two clauses handle code before Erlang/OTP 19.0. fold_record_field({_Name, none} = None) -> None; fold_record_field({Name, F}) -> -- cgit v1.2.3 From a73f32b54f5679a2badb48e080203e28f3b40af7 Mon Sep 17 00:00:00 2001 From: Hans Bolinder Date: Fri, 29 Apr 2016 10:25:00 +0200 Subject: syntax_tools: Correct types and specs --- lib/syntax_tools/src/erl_syntax.erl | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl index 8ca4a8e727..5c252dd749 100644 --- a/lib/syntax_tools/src/erl_syntax.erl +++ b/lib/syntax_tools/src/erl_syntax.erl @@ -5166,7 +5166,7 @@ revert_function_type(Node) -> %% @see function_type/1 %% @see function_type/2 --spec function_type_arguments(syntaxTree()) -> any_arity | syntaxTree(). +-spec function_type_arguments(syntaxTree()) -> any_arity | [syntaxTree()]. function_type_arguments(Node) -> case unwrap(Node) of @@ -5203,33 +5203,34 @@ function_type_return(Node) -> %% @see constraint_body/1 -record(constraint, {name :: syntaxTree(), - type :: syntaxTree()}). + types :: [syntaxTree()]}). %% type(Node) = constraint %% data(Node) = #constraint{name :: Name, -%% type :: Type} +%% types :: [Type]} %% %% Name = syntaxTree() %% Type = syntaxTree() %% %% `erl_parse' representation: %% -%% {type, Pos, constraint, [{atom, Pos, is_subtype}, Name, Type]} +%% {type, Pos, constraint, [Name, [Var, Type]]} %% -%% Name = erl_parse() +%% Name = {atom, Pos, is_subtype} +%% Var = erl_parse() %% Type = erl_parse() --spec constraint(syntaxTree(), syntaxTree()) -> syntaxTree(). +-spec constraint(syntaxTree(), [syntaxTree()]) -> syntaxTree(). -constraint(Name, Type) -> +constraint(Name, Types) -> tree(constraint, - #constraint{name = Name, type = Type}). + #constraint{name = Name, types = Types}). revert_constraint(Node) -> Pos = get_pos(Node), Name = constraint_argument(Node), - Type = constraint_body(Node), - {type, Pos, constraint, [Name, Type]}. + Types = constraint_body(Node), + {type, Pos, constraint, [Name, Types]}. %% ===================================================================== @@ -5252,14 +5253,14 @@ constraint_argument(Node) -> %% %% @see constraint/2 --spec constraint_body(syntaxTree()) -> syntaxTree(). +-spec constraint_body(syntaxTree()) -> [syntaxTree()]. constraint_body(Node) -> case unwrap(Node) of - {type, _, constraint, [_, Type]} -> - Type; + {type, _, constraint, [_, Types]} -> + Types; Node1 -> - (data(Node1))#constraint.type + (data(Node1))#constraint.types end. -- cgit v1.2.3 From db39317ea85f7d8646b3da3d96f2f05954e16665 Mon Sep 17 00:00:00 2001 From: Hans Bolinder Date: Tue, 3 May 2016 10:23:58 +0200 Subject: syntax_tools: Add support for new map type syntax The pretty-printing of `...' in map types is complex. The representation of `...' can be changed before OTP 19. --- lib/syntax_tools/src/erl_prettypr.erl | 57 ++++++++++-- lib/syntax_tools/src/erl_syntax.erl | 167 +++++++++++++++++++++++----------- 2 files changed, 162 insertions(+), 62 deletions(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_prettypr.erl b/lib/syntax_tools/src/erl_prettypr.erl index 9b6c5d977e..119d375746 100644 --- a/lib/syntax_tools/src/erl_prettypr.erl +++ b/lib/syntax_tools/src/erl_prettypr.erl @@ -27,8 +27,6 @@ -module(erl_prettypr). --compile(export_all). - -export([format/1, format/2, best/1, best/2, layout/1, layout/2, get_ctxt_precedence/1, set_ctxt_precedence/2, get_ctxt_paperwidth/1, set_ctxt_paperwidth/2, @@ -1133,20 +1131,25 @@ lay_2(Node, Ctxt) -> text("map()"); Fs -> {Prec, _PrecR} = type_preop_prec('#'), - Es = seq(Fs, - floating(text(",")), reset_prec(Ctxt), - fun lay/2), + Es = lay_map_fields(Fs, + floating(text(",")), + reset_prec(Ctxt)), D = beside(floating(text("#{")), beside(par(Es), floating(text("}")))), maybe_parentheses(D, Prec, Ctxt) end; - map_type_pair -> + map_type_assoc -> + Name = erl_syntax:map_type_assoc_name(Node), + Value = erl_syntax:map_type_assoc_value(Node), + lay_type_assoc(Name, Value, Ctxt); + + map_type_exact -> Ctxt1 = reset_prec(Ctxt), - D1 = lay(erl_syntax:map_type_pair_key(Node), Ctxt1), - D2 = lay(erl_syntax:map_type_pair_value(Node), Ctxt1), - par([D1, floating(text("=>")), D2], Ctxt1#ctxt.break_indent); + D1 = lay(erl_syntax:map_type_exact_name(Node), Ctxt1), + D2 = lay(erl_syntax:map_type_exact_value(Node), Ctxt1), + par([D1, floating(text(":=")), D2], Ctxt1#ctxt.break_indent); integer_range_type -> {PrecL, Prec, PrecR} = type_inop_prec('..'), @@ -1397,6 +1400,42 @@ lay_error_info(T, Ctxt) -> lay_concrete(T, Ctxt) -> lay(erl_syntax:abstract(T), Ctxt). +lay_map_fields([H | T], Separator, Ctxt) -> + case T of + [] -> + [case erl_syntax:type(H) of + map_type_assoc -> + lay_last_type_assoc(H, Ctxt); + _ -> + lay(H, Ctxt) + end]; + _ -> + [maybe_append(Separator, lay(H, Ctxt)) + | lay_map_fields(T, Separator, Ctxt)] + end; +lay_map_fields([], _, _) -> + [empty()]. + +lay_last_type_assoc(Node, Ctxt) -> + Name = erl_syntax:map_type_assoc_name(Node), + Value = erl_syntax:map_type_assoc_value(Node), + IsAny = fun({type,_,any,[]}) -> true; + %% ({var,_,'_'}) -> true; + (_) -> false + end, + case IsAny(Name) andalso IsAny(Value) of + true -> + text("..."); + false -> + lay_type_assoc(Name, Value, Ctxt) + end. + +lay_type_assoc(Name, Value, Ctxt) -> + Ctxt1 = reset_prec(Ctxt), + D1 = lay(Name, Ctxt1), + D2 = lay(Value, Ctxt1), + par([D1, floating(text("=>")), D2], Ctxt1#ctxt.break_indent). + lay_type_application(Name, Arguments, Ctxt) -> {PrecL, Prec} = func_prec(), % D1 = lay(Name, set_prec(Ctxt, PrecL)), diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl index 5c252dd749..f4cda814fc 100644 --- a/lib/syntax_tools/src/erl_syntax.erl +++ b/lib/syntax_tools/src/erl_syntax.erl @@ -253,9 +253,12 @@ map_type/0, map_type/1, map_type_fields/1, - map_type_pair/2, - map_type_pair_key/1, - map_type_pair_value/1, + map_type_assoc/2, + map_type_assoc_name/1, + map_type_assoc_value/1, + map_type_exact/2, + map_type_exact_name/1, + map_type_exact_value/1, match_expr/2, match_expr_body/1, match_expr_pattern/1, @@ -509,7 +512,8 @@ %% map_field_exact %% %% map_type -%% map_type_pair +%% map_type_assoc +%% map_type_exact %% match_expr %% module_qualifier %% @@ -596,7 +600,8 @@ %% @see map_field_exact/2 %% @see map_type/0 %% @see map_type/1 -%% @see map_type_pair/2 +%% @see map_type_assoc/2 +%% @see map_type_exact/2 %% @see match_expr/2 %% @see module_qualifier/2 %% @see named_fun_expr/2 @@ -697,7 +702,8 @@ type(Node) -> {type, _, 'fun', []} -> fun_type; {type, _, 'fun', [_, _]} -> function_type; {type, _, map, _} -> map_type; - {type, _, map_field_assoc, _} -> map_type_pair; + {type, _, map_field_assoc, _} -> map_type_assoc; + {type, _, map_field_exact, _} -> map_type_exact; {type, _, record, _} -> record_type; {type, _, field_type, _} -> record_type_field; {type, _, range, _} -> integer_range_type; @@ -5265,69 +5271,118 @@ constraint_body(Node) -> %% ===================================================================== -%% @doc Creates an abstract type map assoc field. The result represents -%% "KeyType => ValueType". +%% @doc Creates an abstract map type assoc field. The result represents +%% "Name => Value". %% -%% @see map_type_pair_key/1 -%% @see map_type_pair_value/1 +%% @see map_type_assoc_name/1 +%% @see map_type_assoc_value/1 +%% @see map_type/1 --record(map_type_pair, {key :: syntaxTree(), - value :: syntaxTree()}). +-record(map_type_assoc, {name :: syntaxTree(), value :: syntaxTree()}). -%% type(Node) = map_type_pair -%% data(Node) = #map_type_pair{key :: KeyType, -%% value :: ValueType} +%% `erl_parse' representation: %% -%% KeyType = syntaxTree() -%% ValueType = syntaxTree() +%% {type, Pos, map_field_assoc, [Name, Value]} + +-spec map_type_assoc(syntaxTree(), syntaxTree()) -> syntaxTree(). + +map_type_assoc(Name, Value) -> + tree(map_type_assoc, #map_type_assoc{name = Name, value = Value}). + +revert_map_type_assoc(Node) -> + Pos = get_pos(Node), + Name = map_type_assoc_name(Node), + Value = map_type_assoc_value(Node), + {type, Pos, map_type_assoc, [Name, Value]}. + + +%% ===================================================================== +%% @doc Returns the name subtree of a `map_type_assoc' node. %% -%% `erl_parse' representation: +%% @see map_type_assoc/2 + +-spec map_type_assoc_name(syntaxTree()) -> syntaxTree(). + +map_type_assoc_name(Node) -> + case Node of + {type, _, map_field_assoc, [Name, _]} -> + Name; + _ -> + (data(Node))#map_type_assoc.name + end. + + +%% ===================================================================== +%% @doc Returns the value subtree of a `map_type_assoc' node. %% -%% {type, Pos, map_field_assoc, [KeyType, ValueType]} +%% @see map_type_assoc/2 + +-spec map_type_assoc_value(syntaxTree()) -> syntaxTree(). + +map_type_assoc_value(Node) -> + case Node of + {type, _, map_field_assoc, [_, Value]} -> + Value; + _ -> + (data(Node))#map_type_assoc.value + end. + + +%% ===================================================================== +%% @doc Creates an abstract map type exact field. The result represents +%% "Name := Value". %% -%% KeyType = erl_parse() -%% ValueType = erl_parse() +%% @see map_type_exact_name/1 +%% @see map_type_exact_value/1 +%% @see map_type/1 + +-record(map_type_exact, {name :: syntaxTree(), value :: syntaxTree()}). + +%% `erl_parse' representation: +%% +%% {type, Pos, map_field_exact, [Name, Value]} --spec map_type_pair(syntaxTree(), syntaxTree()) -> syntaxTree(). +-spec map_type_exact(syntaxTree(), syntaxTree()) -> syntaxTree(). -map_type_pair(KeyType, ValueType) -> - tree(map_type_pair, - #map_type_pair{key = KeyType, value = ValueType}). +map_type_exact(Name, Value) -> + tree(map_type_exact, #map_type_exact{name = Name, value = Value}). -revert_map_type_pair(Node) -> +revert_map_type_exact(Node) -> Pos = get_pos(Node), - KeyType = map_type_pair_key(Node), - ValueType = map_type_pair_value(Node), - {type, Pos, map_field_assoc, [KeyType, ValueType]}. + Name = map_type_exact_name(Node), + Value = map_type_exact_value(Node), + {type, Pos, map_type_exact, [Name, Value]}. + %% ===================================================================== -%% @doc Returns the key type subtrees of a `map_type_pair' node. +%% @doc Returns the name subtree of a `map_type_exact' node. %% -%% @see map_type_pair/2 +%% @see map_type_exact/2 --spec map_type_pair_key(syntaxTree()) -> syntaxTree(). +-spec map_type_exact_name(syntaxTree()) -> syntaxTree(). -map_type_pair_key(Node) -> - case unwrap(Node) of - {type, _, map_field_assoc, [KeyType, _]} -> - KeyType; - Node1 -> - (data(Node1))#map_type_pair.key +map_type_exact_name(Node) -> + case Node of + {type, _, map_field_exact, [Name, _]} -> + Name; + _ -> + (data(Node))#map_type_exact.name end. + %% ===================================================================== -%% @doc Returns the value type subtrees of a `map_type_pair' node. +%% @doc Returns the value subtree of a `map_type_exact' node. %% -%% @see map_type_pair/2 +%% @see map_type_exact/2 --spec map_type_pair_value(syntaxTree()) -> syntaxTree(). +-spec map_type_exact_value(syntaxTree()) -> syntaxTree(). -map_type_pair_value(Node) -> - case unwrap(Node) of - {type, _, map_field_assoc, [_, ValueType]} -> - ValueType; - Node1 -> - (data(Node1))#map_type_pair.value +map_type_exact_value(Node) -> + case Node of + {type, _, map_field_exact, [_, Value]} -> + Value; + _ -> + (data(Node))#map_type_exact.value end. @@ -7428,8 +7483,10 @@ revert_root(Node) -> revert_map_field_exact(Node); map_type -> revert_map_type(Node); - map_type_pair -> - revert_map_type_pair(Node); + map_type_assoc -> + revert_map_type_assoc(Node); + map_type_exact -> + revert_map_type_exact(Node); match_expr -> revert_match_expr(Node); module_qualifier -> @@ -7721,9 +7778,12 @@ subtrees(T) -> [map_field_exact_value(T)]]; map_type -> [map_type_fields(T)]; - map_type_pair -> - [[map_type_pair_key(T)], - [map_type_pair_value(T)]]; + map_type_assoc -> + [[map_type_assoc_name(T)], + [map_type_assoc_value(T)]]; + map_type_exact -> + [[map_type_exact_name(T)], + [map_type_exact_value(T)]]; match_expr -> [[match_expr_pattern(T)], [match_expr_body(T)]]; @@ -7886,7 +7946,8 @@ make_tree(map_expr, [[E], Fs]) -> map_expr(E, Fs); make_tree(map_field_assoc, [[K], [V]]) -> map_field_assoc(K, V); make_tree(map_field_exact, [[K], [V]]) -> map_field_exact(K, V); make_tree(map_type, [Fs]) -> map_type(Fs); -make_tree(map_type_pair, [[K],[V]]) -> map_type_pair(K, V); +make_tree(map_type_assoc, [[N],[V]]) -> map_type_assoc(N, V); +make_tree(map_type_exact, [[N],[V]]) -> map_type_exact(N, V); make_tree(match_expr, [[P], [E]]) -> match_expr(P, E); make_tree(named_fun_expr, [[N], C]) -> named_fun_expr(N, C); make_tree(module_qualifier, [[M], [N]]) -> module_qualifier(M, N); -- cgit v1.2.3 From fb49e45402c0370c030ce3c128b08bccf960bf44 Mon Sep 17 00:00:00 2001 From: Hans Bolinder Date: Thu, 19 May 2016 08:59:45 +0200 Subject: stdlib: Correct types and specs --- lib/syntax_tools/src/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/Makefile b/lib/syntax_tools/src/Makefile index 2e91adf8af..8325db45a8 100644 --- a/lib/syntax_tools/src/Makefile +++ b/lib/syntax_tools/src/Makefile @@ -29,7 +29,7 @@ ERL_COMPILE_FLAGS += -pa $(EBIN) -pa ./ -I$(INCLUDE) ifeq ($(NATIVE_LIBS_ENABLED),yes) ERL_COMPILE_FLAGS += +native endif -ERL_COMPILE_FLAGS += +nowarn_shadow_vars +warn_unused_import -Werror # +warn_missing_spec +warn_untyped_record +ERL_COMPILE_FLAGS += +nowarn_shadow_vars +warn_unused_import #-Werror # +warn_missing_spec +warn_untyped_record SOURCES=erl_syntax.erl erl_prettypr.erl erl_syntax_lib.erl \ erl_comment_scan.erl erl_recomment.erl erl_tidy.erl \ -- cgit v1.2.3 From 92ab1d0966b13a2a3ca7e77e1196d0099b633668 Mon Sep 17 00:00:00 2001 From: Hans Bolinder Date: Thu, 19 May 2016 14:03:41 +0200 Subject: syntax_tools: Correct types --- lib/syntax_tools/src/erl_recomment.erl | 8 ++++---- lib/syntax_tools/src/erl_syntax.erl | 9 ++++++++- 2 files changed, 12 insertions(+), 5 deletions(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_recomment.erl b/lib/syntax_tools/src/erl_recomment.erl index 5ce533285d..c1141b2bc6 100644 --- a/lib/syntax_tools/src/erl_recomment.erl +++ b/lib/syntax_tools/src/erl_recomment.erl @@ -601,16 +601,16 @@ expand_comment(C) -> -record(leaf, {min = 0 :: integer(), max = 0 :: integer(), - precomments = [] :: [erl_syntax:syntaxTree()], - postcomments = [] :: [erl_syntax:syntaxTree()], + precomments = [] :: [erl_comment_scan:comment()], + postcomments = [] :: [erl_comment_scan:comment()], value :: erl_syntax:syntaxTree()}). -record(tree, {min = 0 :: integer(), max = 0 :: integer(), type :: atom(), attrs :: erl_syntax:syntaxTreeAttributes(), - precomments = [] :: [erl_syntax:syntaxTree()], - postcomments = [] :: [erl_syntax:syntaxTree()], + precomments = [] :: [erl_comment_scan:comment()], + postcomments = [] :: [erl_comment_scan:comment()], subtrees = [] :: [extendedSyntaxTree()]}). diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl index f4cda814fc..ee42e56172 100644 --- a/lib/syntax_tools/src/erl_syntax.erl +++ b/lib/syntax_tools/src/erl_syntax.erl @@ -443,7 +443,14 @@ -type syntaxTree() :: #tree{} | #wrapper{} | erl_parse(). --type erl_parse() :: erl_parse:abstract_form() | erl_parse:abstract_expr(). +-type erl_parse() :: erl_parse:abstract_clause() + | erl_parse:abstract_expr() + | erl_parse:abstract_form() + | erl_parse:abstract_type() + | erl_parse:form_info() + %% To shut up Dialyzer: + | {bin_element, _, _, _, _}. + %% The representation built by the Erlang standard library parser %% `erl_parse'. This is a subset of the {@link syntaxTree()} type. -- cgit v1.2.3 From af761914c9a94a9d54c1bcc2d4fff96a58674078 Mon Sep 17 00:00:00 2001 From: Hans Bolinder Date: Thu, 9 Jun 2016 13:18:07 +0200 Subject: Remove support for '...' in Maps types It is possible that '...' is added later (OTP 20.0), but for now we are not sure of all details. --- lib/syntax_tools/src/erl_prettypr.erl | 39 +++++------------------------------ 1 file changed, 5 insertions(+), 34 deletions(-) (limited to 'lib/syntax_tools/src') diff --git a/lib/syntax_tools/src/erl_prettypr.erl b/lib/syntax_tools/src/erl_prettypr.erl index 119d375746..f1615b2610 100644 --- a/lib/syntax_tools/src/erl_prettypr.erl +++ b/lib/syntax_tools/src/erl_prettypr.erl @@ -1130,13 +1130,14 @@ lay_2(Node, Ctxt) -> any_size -> text("map()"); Fs -> - {Prec, _PrecR} = type_preop_prec('#'), - Es = lay_map_fields(Fs, - floating(text(",")), - reset_prec(Ctxt)), + Ctxt1 = reset_prec(Ctxt), + Es = seq(Fs, + floating(text(",")), Ctxt1, + fun lay/2), D = beside(floating(text("#{")), beside(par(Es), floating(text("}")))), + {Prec, _PrecR} = type_preop_prec('#'), maybe_parentheses(D, Prec, Ctxt) end; @@ -1400,36 +1401,6 @@ lay_error_info(T, Ctxt) -> lay_concrete(T, Ctxt) -> lay(erl_syntax:abstract(T), Ctxt). -lay_map_fields([H | T], Separator, Ctxt) -> - case T of - [] -> - [case erl_syntax:type(H) of - map_type_assoc -> - lay_last_type_assoc(H, Ctxt); - _ -> - lay(H, Ctxt) - end]; - _ -> - [maybe_append(Separator, lay(H, Ctxt)) - | lay_map_fields(T, Separator, Ctxt)] - end; -lay_map_fields([], _, _) -> - [empty()]. - -lay_last_type_assoc(Node, Ctxt) -> - Name = erl_syntax:map_type_assoc_name(Node), - Value = erl_syntax:map_type_assoc_value(Node), - IsAny = fun({type,_,any,[]}) -> true; - %% ({var,_,'_'}) -> true; - (_) -> false - end, - case IsAny(Name) andalso IsAny(Value) of - true -> - text("..."); - false -> - lay_type_assoc(Name, Value, Ctxt) - end. - lay_type_assoc(Name, Value, Ctxt) -> Ctxt1 = reset_prec(Ctxt), D1 = lay(Name, Ctxt1), -- cgit v1.2.3