%% @doc Rebar3 Pretty Printing of abstract Erlang syntax trees,
%% based on original erl_prettypr.
%%
%% It was taken verbatim from erl_prettypr
%% and it was modified just to fix some bugs.
%%
%% This module is a front end to the pretty-printing library module
%% `prettypr', for text formatting of abstract syntax trees defined by
%% the module `erl_syntax'.
-module(otp_formatter).
%% Allow erl_syntax:syntaxTree/0 type spec and allow us to preserve original OTP code
-elvis([{elvis_style, atom_naming_convention, #{regex => "^([a-zA-Z][a-z0-9]*_?)*$"}},
{elvis_style, dont_repeat_yourself, #{min_complexity => 20}},
{elvis_style, no_catch_expressions, disable},
{elvis_style, no_import, disable},
{elvis_style, export_used_types, disable}]).
-format #{inline_clause_bodies => true, unquote_atoms => false}.
%% erl_syntax functions that only appear in OTP25
-dialyzer([no_missing_calls]).
-behaviour(rebar3_formatter).
-behaviour(rebar3_ast_formatter).
-export([init/2, format_file/3, format/1, format/3, best/1, best/2, layout/1, layout/2,
get_ctxt_precedence/1, set_ctxt_precedence/2, get_ctxt_paperwidth/1,
set_ctxt_paperwidth/2, get_ctxt_linewidth/1, set_ctxt_linewidth/2, get_ctxt_hook/1,
set_ctxt_hook/2, get_ctxt_user/1, set_ctxt_user/2]).
-import(prettypr,
[text/1,
nest/2,
above/2,
beside/2,
sep/1,
par/1,
par/2,
floating/3,
floating/1,
break/1,
follow/2,
follow/3,
empty/0]).
-import(erl_parse,
[preop_prec/1,
inop_prec/1,
func_prec/0,
max_prec/0,
type_inop_prec/1,
type_preop_prec/1]).
-define(PADDING, 2).
-define(PAPER, 80).
-define(RIBBON, 56).
-define(NOUSER, undefined).
-define(NOHOOK, none).
-type hook() :: none | fun((erl_syntax:syntaxTree(), _, _) -> prettypr:document()).
-type clause_t() ::
case_expr |
fun_expr |
if_expr |
maybe_expr |
receive_expr |
try_expr |
{function, prettypr:document()} |
spec.
-record(ctxt,
{prec = 0 :: integer(),
sub_indent = 2 :: non_neg_integer(),
break_indent = 4 :: non_neg_integer(),
clause = undefined :: clause_t() | undefined,
hook = ?NOHOOK :: hook(),
paper = ?PAPER :: integer(),
ribbon = ?RIBBON :: integer(),
user = ?NOUSER :: term(),
encoding = epp:default_encoding() :: epp:source_encoding(),
empty_lines = sets:new() :: sets:set(integer())}).
-type context() :: #ctxt{}.
%% =====================================================================
%% The following functions examine and modify contexts:
%% @doc Returns the operator precedence field of the pretty-printer
%% context.
%%
%% @see set_ctxt_precedence/2
-spec get_ctxt_precedence(context()) -> integer().
get_ctxt_precedence(Ctxt) -> Ctxt#ctxt.prec.
%%
%% @doc Updates the operator precedence field of the pretty-printer
%% context. See the {@link //stdlib/erl_parse} module for operator precedences.
%%
%% @see //stdlib/erl_parse
%% @see get_ctxt_precedence/1
-spec set_ctxt_precedence(context(), integer()) -> context().
set_ctxt_precedence(Ctxt, Prec) -> set_prec(Ctxt, Prec).
set_prec(Ctxt, Prec) ->
Ctxt#ctxt{prec = Prec}. % used internally
reset_prec(Ctxt) ->
set_prec(Ctxt, 0). % used internally
%% @doc Returns the paper width field of the pretty-printer context.
%% @see set_ctxt_paperwidth/2
-spec get_ctxt_paperwidth(context()) -> integer().
get_ctxt_paperwidth(Ctxt) -> Ctxt#ctxt.paper.
%%
%% @doc Updates the paper width field of the pretty-printer context.
%%
%% Note: changing this value (and passing the resulting context to a
%% continuation function) does not affect the normal formatting, but may
%% affect user-defined behaviour in hook functions.
%%
%% @see get_ctxt_paperwidth/1
-spec set_ctxt_paperwidth(context(), integer()) -> context().
set_ctxt_paperwidth(Ctxt, W) -> Ctxt#ctxt{paper = W}.
%% @doc Returns the line width field of the pretty-printer context.
%% @see set_ctxt_linewidth/2
-spec get_ctxt_linewidth(context()) -> integer().
get_ctxt_linewidth(Ctxt) -> Ctxt#ctxt.ribbon.
%%
%% @doc Updates the line width field of the pretty-printer context.
%%
%% Note: changing this value (and passing the resulting context to a
%% continuation function) does not affect the normal formatting, but may
%% affect user-defined behaviour in hook functions.
%%
%% @see get_ctxt_linewidth/1
-spec set_ctxt_linewidth(context(), integer()) -> context().
set_ctxt_linewidth(Ctxt, W) -> Ctxt#ctxt{ribbon = W}.
%% @doc Returns the hook function field of the pretty-printer context.
%% @see set_ctxt_hook/2
-spec get_ctxt_hook(context()) -> hook().
get_ctxt_hook(Ctxt) -> Ctxt#ctxt.hook.
%% @doc Updates the hook function field of the pretty-printer context.
%% @see get_ctxt_hook/1
-spec set_ctxt_hook(context(), hook()) -> context().
set_ctxt_hook(Ctxt, Hook) -> Ctxt#ctxt{hook = Hook}.
%% @doc Returns the user data field of the pretty-printer context.
%% @see set_ctxt_user/2
-spec get_ctxt_user(context()) -> term().
get_ctxt_user(Ctxt) -> Ctxt#ctxt.user.
%% @doc Updates the user data field of the pretty-printer context.
%% @see get_ctxt_user/1
-spec set_ctxt_user(context(), term()) -> context().
set_ctxt_user(Ctxt, X) -> Ctxt#ctxt{user = X}.
%% =====================================================================
%% rebar3_formatter CALLBACKS
%% =====================================================================
%% @doc Initialize the formatter and generate a state that will be passed in when
%% calling other callbacks.
-spec init(rebar3_formatter:opts(), undefined | rebar_state:t()) -> nostate.
init(_, _) -> nostate.
%% @doc Format a file.
%% Apply formatting rules to a file containing erlang code.
%% Use <code>Opts</code> to configure the formatter.
-spec format_file(file:filename_all(), nostate, rebar3_formatter:opts()) ->
rebar3_formatter:result().
format_file(File, nostate, Opts) -> rebar3_ast_formatter:format(File, ?MODULE, Opts).
%% =====================================================================
%% @equiv format(Tree, [])
-spec format(erl_syntax:syntaxTree()) -> string().
format(Node) -> format(Node, [], #{}).
%% =====================================================================
%%
%% @doc Pretty-prints/formats an abstract Erlang syntax tree as text. For
%% example, if you have a `.beam' file that has been compiled with
%% `debug_info', the following should print the source code for the
%% module (as it looks in the debug info representation):
%% ```{ok,{_,[{abstract_code,{_,AC}}]}} =
%% beam_lib:chunks("myfile.beam",[abstract_code]),
%% io:put_chars(otp_formatter:format(erl_syntax:form_list(AC), [], #{}))
%% '''
%%
%% Available options:
%% <dl>
%% <dt>{hook, none | {@link hook()}}</dt>
%% <dd>Unless the value is `none', the given function is called
%% for each node whose list of annotations is not empty; see below
%% for details. The default value is `none'.</dd>
%%
%% <dt>{paper, integer()}</dt>
%% <dd>Specifies the preferred maximum number of characters on any
%% line, including indentation. The default value is 80.</dd>
%%
%% <dt>{ribbon, integer()}</dt>
%% <dd>Specifies the preferred maximum number of characters on any
%% line, not counting indentation. The default value is 65.</dd>
%%
%% <dt>{user, term()}</dt>
%% <dd>User-specific data for use in hook functions. The default
%% value is `undefined'.</dd>
%% <dt>{encoding, epp:source_encoding()}</dt>
%% <dd>Specifies the encoding of the generated file.</dd>
%% </dl>
%%
%% A hook function (cf. the {@link hook()} type) is passed the current
%% syntax tree node, the context, and a continuation. The context can be
%% examined and manipulated by functions such as `get_ctxt_user/1' and
%% `set_ctxt_user/2'. The hook must return a "document" data structure
%% (see {@link layout/2} and {@link best/2}); this may be constructed in
%% part or in whole by applying the continuation function. For example,
%% the following is a trivial hook:
%% ```
%% fun (Node, Ctxt, Cont) -> Cont(Node, Ctxt) end
%% '''
%% which yields the same result as if no hook was given.
%% The following, however:
%% ```
%% fun (Node, Ctxt, Cont) ->
%% Doc = Cont(Node, Ctxt),
%% prettypr:beside(prettypr:text("<b>"),
%% prettypr:beside(Doc,
%% prettypr:text("</b>")))
%% end
%% '''
%% will place the text of any annotated node (regardless of the
%% annotation data) between HTML "boldface begin" and "boldface end"
%% tags.
%%
%% @see erl_syntax
%% @see format/1
%% @see layout/2
%% @see best/2
%% @see get_ctxt_user/1
%% @see set_ctxt_user/2
-spec format(erl_syntax:syntaxTree(), [pos_integer()], rebar3_formatter:opts()) ->
string().
format(Node, EmptyLines, Options) ->
W = maps:get(paper, Options, ?PAPER),
L = maps:get(ribbon, Options, ?RIBBON),
E = maps:get(encoding, Options, utf8),
OptList = [{empty_lines, sets:from_list(EmptyLines)} | maps:to_list(Options)],
PreFormatted = prettypr:format(layout(Node, OptList), W, L),
binary_to_list(unicode:characters_to_binary(PreFormatted, E)).
%% =====================================================================
%% @equiv best(Tree, [])
-spec best(erl_syntax:syntaxTree()) -> empty | prettypr:document().
best(Node) -> best(Node, []).
%% =====================================================================
%% empty | prettypr:document()
%%
%% @doc Creates a fixed "best" abstract layout for a syntax tree. This
%% is similar to the `layout/2' function, except that here, the final
%% layout has been selected with respect to the given options. The atom
%% `empty' is returned if no such layout could be produced. For
%% information on the options, see the `format/2' function.
%%
%% @see best/1
%% @see layout/2
%% @see format/2
%% @see prettypr:best/3
-spec best(erl_syntax:syntaxTree(), [term()]) -> empty | prettypr:document().
best(Node, Options) ->
W = proplists:get_value(paper, Options, ?PAPER),
L = proplists:get_value(ribbon, Options, ?RIBBON),
prettypr:best(layout(Node, Options), W, L).
%% =====================================================================
%% @equiv layout(Tree, [])
-spec layout(erl_syntax:syntaxTree()) -> prettypr:document().
layout(Node) -> layout(Node, []).
%% =====================================================================
%%
%% @doc Creates an abstract document layout for a syntax tree. The
%% result represents a set of possible layouts (cf. module `prettypr').
%% For information on the options, see {@link format/2}; note, however,
%% that the `paper' and `ribbon' options are ignored by this function.
%%
%% This function provides a low-level interface to the pretty printer,
%% returning a flexible representation of possible layouts, independent
%% of the paper width eventually to be used for formatting. This can be
%% included as part of another document and/or further processed
%% directly by the functions in the `prettypr' module, or used in a hook
%% function (see `format/2' for details).
%%
%% @see prettypr
%% @see format/2
%% @see layout/1
-spec layout(erl_syntax:syntaxTree(), [term()]) -> prettypr:document().
layout(Node, Options) ->
lay(Node,
#ctxt{hook = proplists:get_value(hook, Options, ?NOHOOK),
paper = proplists:get_value(paper, Options, ?PAPER),
ribbon = proplists:get_value(ribbon, Options, ?RIBBON),
user = proplists:get_value(user, Options),
encoding = proplists:get_value(encoding, Options, epp:default_encoding()),
empty_lines = proplists:get_value(empty_lines, Options, sets:new())}).
lay(Node, Ctxt) ->
case erl_syntax:get_ann(Node) of
[] ->
%% Hooks are not called if there are no annotations.
do_lay(Node, Ctxt);
_As ->
case Ctxt#ctxt.hook of
?NOHOOK -> do_lay(Node, Ctxt);
Hook -> Hook(Node, Ctxt, fun do_lay/2)
end
end.
%% This handles attached comments:
do_lay(Node, Ctxt) ->
case erl_syntax:has_comments(Node) of
true ->
D1 = lay_no_comments(Node, Ctxt),
D2 = lay_postcomments(erl_syntax:get_postcomments(Node), D1),
lay_precomments(erl_syntax:get_precomments(Node), D2);
false -> lay_no_comments(Node, Ctxt)
end.
%% For pre-comments, all padding is ignored.
lay_precomments([], D) -> D;
lay_precomments(Cs, D) -> above(floating(break(stack_comments(Cs, false)), -1, -1), D).
%% For postcomments, individual padding is added.
lay_postcomments([], D) -> D;
lay_postcomments(Cs, D) -> beside(D, floating(break(stack_comments(Cs, true)), 1, 0)).
%% Format (including padding, if `Pad' is `true', otherwise not)
%% and stack the listed comments above each other.
stack_comments([C | Cs], Pad) ->
D = stack_comment_lines(erl_syntax:comment_text(C)),
D1 = case Pad of
true ->
P = case erl_syntax:comment_padding(C) of
none -> ?PADDING;
P1 -> P1
end,
beside(text(spaces(P)), D);
false -> D
end,
case Cs of
[] ->
D1; % done
_ -> above(D1, stack_comments(Cs, Pad))
end.
%% Stack lines of text above each other and prefix each string in
%% the list with a single `%' character.
stack_comment_lines([S | Ss]) ->
D = text(add_comment_prefix(S)),
case Ss of
[] -> D;
_ -> above(D, stack_comment_lines(Ss))
end;
stack_comment_lines([]) -> empty().
add_comment_prefix(S) -> [$% | S].
%% This part ignores annotations and comments:
lay_no_comments(Node, Ctxt) ->
case erl_syntax:type(Node) of
%% We list literals and other common cases first.
variable -> text(erl_syntax:variable_literal(Node));
atom -> text(erl_syntax:atom_literal(Node, Ctxt#ctxt.encoding));
integer -> text(tidy_integer(Node));
float -> text(tidy_float(Node));
char -> text(erl_syntax:char_literal(Node, Ctxt#ctxt.encoding));
string -> lay_string(erl_syntax:string_literal(Node, Ctxt#ctxt.encoding), Ctxt);
nil -> text("[]");
tuple ->
Es = seq(erl_syntax:tuple_elements(Node),
lay_text_float(","),
reset_prec(Ctxt),
fun lay/2),
beside(lay_text_float("{"), beside(sep(Es), lay_text_float("}")));
list ->
Ctxt1 = reset_prec(Ctxt),
Node1 = erl_syntax:compact_list(Node),
D1 = sep(seq(erl_syntax:list_prefix(Node1), lay_text_float(","), Ctxt1, fun lay/2)),
D = case erl_syntax:list_suffix(Node1) of
none -> beside(D1, lay_text_float("]"));
S ->
follow(D1,
beside(lay_text_float("| "),
beside(lay(S, Ctxt1), lay_text_float("]"))))
end,
beside(lay_text_float("["), D);
operator -> lay_text_float(erl_syntax:operator_literal(Node));
infix_expr ->
Operator = erl_syntax:infix_expr_operator(Node),
{PrecL, Prec, PrecR} =
case erl_syntax:type(Operator) of
operator -> inop_prec(erl_syntax:operator_name(Operator));
_ -> {0, 0, 0}
end,
D1 = lay(erl_syntax:infix_expr_left(Node), set_prec(Ctxt, PrecL)),
D2 = lay(Operator, reset_prec(Ctxt)),
D3 = lay(erl_syntax:infix_expr_right(Node), set_prec(Ctxt, PrecR)),
D4 = par([D1, D2, D3], Ctxt#ctxt.break_indent),
maybe_parentheses(D4, Prec, Ctxt);
prefix_expr ->
Operator = erl_syntax:prefix_expr_operator(Node),
{{Prec, PrecR}, Name} =
case erl_syntax:type(Operator) of
operator ->
N = erl_syntax:operator_name(Operator),
{preop_prec(N), N};
_ -> {{0, 0}, any}
end,
D1 = lay(Operator, reset_prec(Ctxt)),
D2 = lay(erl_syntax:prefix_expr_argument(Node), set_prec(Ctxt, PrecR)),
D3 = case Name of
'+' -> beside(D1, D2);
'-' -> beside(D1, D2);
_ -> par([D1, D2], Ctxt#ctxt.break_indent)
end,
maybe_parentheses(D3, Prec, Ctxt);
application ->
{PrecL, Prec} = func_prec(),
D = lay(erl_syntax:application_operator(Node), set_prec(Ctxt, PrecL)),
As = seq(erl_syntax:application_arguments(Node),
floating(text(",")),
reset_prec(Ctxt),
fun lay/2),
D1 = beside(D, beside(text("("), beside(sep(As), floating(text(")"))))),
maybe_parentheses(D1, Prec, Ctxt);
match_expr ->
{PrecL, Prec, PrecR} = inop_prec('='),
D1 = lay(erl_syntax:match_expr_pattern(Node), set_prec(Ctxt, PrecL)),
D2 = lay(erl_syntax:match_expr_body(Node), set_prec(Ctxt, PrecR)),
D3 = follow(beside(D1, lay_text_float(" =")), D2, Ctxt#ctxt.break_indent),
maybe_parentheses(D3, Prec, Ctxt);
underscore -> text("_");
clause ->
%% The style used for a clause depends on its context
Ctxt1 = (reset_prec(Ctxt))#ctxt{clause = undefined},
D1 = par(seq(erl_syntax:clause_patterns(Node), lay_text_float(","), Ctxt1, fun lay/2)),
D2 = case erl_syntax:clause_guard(Node) of
none -> none;
G -> lay(G, Ctxt1)
end,
D3 = lay_clause_expressions(erl_syntax:clause_body(Node), Ctxt1),
case Ctxt#ctxt.clause of
fun_expr -> make_fun_clause(D1, D2, D3, Ctxt);
{function, N} -> make_fun_clause(N, D1, D2, D3, Ctxt);
if_expr -> make_if_clause(D2, D3, Ctxt);
case_expr -> make_case_clause(D1, D2, D3, Ctxt);
maybe_expr -> make_case_clause(D1, D2, D3, Ctxt);
receive_expr -> make_case_clause(D1, D2, D3, Ctxt);
try_expr -> make_case_clause(D1, D2, D3, Ctxt);
undefined ->
%% If a clause is formatted out of context, we
%% use a "fun-expression" clause style.
make_fun_clause(D1, D2, D3, Ctxt)
end;
function ->
%% Comments on the name itself will be repeated for each
%% clause, but that seems to be the best way to handle it.
Ctxt1 = reset_prec(Ctxt),
D1 = lay(erl_syntax:function_name(Node), Ctxt1),
D2 = lay_clauses(erl_syntax:function_clauses(Node), {function, D1}, Ctxt1),
beside(D2, lay_text_float("."));
case_expr ->
Ctxt1 = reset_prec(Ctxt),
D1 = lay(erl_syntax:case_expr_argument(Node), Ctxt1),
D2 = lay_clauses(erl_syntax:case_expr_clauses(Node), case_expr, Ctxt1),
sep([par([follow(text("case"), D1, Ctxt1#ctxt.break_indent), text("of")],
Ctxt1#ctxt.break_indent),
nest(Ctxt1#ctxt.break_indent, D2),
text("end")]);
if_expr ->
Ctxt1 = reset_prec(Ctxt),
D = lay_clauses(erl_syntax:if_expr_clauses(Node), if_expr, Ctxt1),
sep([follow(text("if"), D, Ctxt1#ctxt.break_indent), text("end")]);
fun_expr ->
Ctxt1 = reset_prec(Ctxt),
Clauses = lay_clauses(erl_syntax:fun_expr_clauses(Node), fun_expr, Ctxt1),
lay_fun_sep(Clauses, Ctxt1);
named_fun_expr ->
Ctxt1 = reset_prec(Ctxt),
D1 = lay(erl_syntax:named_fun_expr_name(Node), Ctxt1),
Clauses = lay_clauses(erl_syntax:named_fun_expr_clauses(Node), {function, D1}, Ctxt1),
lay_fun_sep(Clauses, Ctxt1);
module_qualifier ->
{PrecL, _Prec, PrecR} = inop_prec(':'),
D1 = lay(erl_syntax:module_qualifier_argument(Node), set_prec(Ctxt, PrecL)),
D2 = lay(erl_syntax:module_qualifier_body(Node), set_prec(Ctxt, PrecR)),
beside(D1, beside(text(":"), D2));
maybe_expr ->
Ctxt1 = reset_prec(Ctxt),
D1 = vertical(seq(erl_syntax:maybe_expr_body(Node),
floating(text(",")),
Ctxt1,
fun lay/2)),
Es0 = [text("end")],
Es1 = case erl_syntax:maybe_expr_else(Node) of
none -> Es0;
ElseNode ->
ElseCs = erl_syntax:else_expr_clauses(ElseNode),
D3 = lay_clauses(ElseCs, maybe_expr, Ctxt1),
[text("else"), nest(Ctxt1#ctxt.break_indent, D3) | Es0]
end,
sep([par([text("maybe"), nest(Ctxt1#ctxt.break_indent, D1), hd(Es1)]) | tl(Es1)]);
maybe_match_expr ->
{PrecL, Prec, PrecR} = inop_prec('='),
D1 = lay(erl_syntax:maybe_match_expr_pattern(Node), set_prec(Ctxt, PrecL)),
D2 = lay(erl_syntax:maybe_match_expr_body(Node), set_prec(Ctxt, PrecR)),
D3 = follow(beside(D1, floating(text(" ?="))), D2, Ctxt#ctxt.break_indent),
maybe_parentheses(D3, Prec, Ctxt);
%%
%% The rest is in alphabetical order (except map and types)
%%
arity_qualifier ->
Ctxt1 = reset_prec(Ctxt),
D1 = lay(erl_syntax:arity_qualifier_body(Node), Ctxt1),
D2 = lay(erl_syntax:arity_qualifier_argument(Node), Ctxt1),
beside(D1, beside(text("/"), D2));
attribute ->
%% The attribute name and arguments are formatted similar to
%% a function call, but prefixed with a "-" and followed by
%% a period. If the arguments is `none', we only output the
%% attribute name, without following parentheses.
Ctxt1 = reset_prec(Ctxt),
Args = erl_syntax:attribute_arguments(Node),
N = case erl_syntax:attribute_name(Node) of
{atom, _, 'if'} -> erl_syntax:variable('if');
N0 -> N0
end,
D = case attribute_type(Node) of
spec ->
[SpecTuple] = Args,
[FuncName, FuncTypes] = erl_syntax:tuple_elements(SpecTuple),
Name = get_func_node(FuncName),
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, lay_text_float(" :: ")),
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), lay_text_float(")"))));
_ when Args =:= none -> lay(N, Ctxt1);
_ ->
D1 = sep(seq(Args, lay_text_float(","), Ctxt1, fun lay/2)),
beside(lay(N, Ctxt1), beside(text("("), beside(D1, lay_text_float(")"))))
end,
beside(lay_text_float("-"), beside(D, lay_text_float(".")));
binary ->
Ctxt1 = reset_prec(Ctxt),
Es = seq(erl_syntax:binary_fields(Node), lay_text_float(","), Ctxt1, fun lay/2),
beside(lay_text_float("<<"), beside(par(Es), lay_text_float(">>")));
binary_field ->
Ctxt1 = set_prec(Ctxt, max_prec()),
D1 = lay(erl_syntax:binary_field_body(Node), Ctxt1),
D2 = case erl_syntax:binary_field_types(Node) of
[] -> empty();
Ts -> beside(lay_text_float("/"), lay_bit_types(Ts, Ctxt1))
end,
beside(D1, D2);
block_expr ->
Ctxt1 = reset_prec(Ctxt),
Es = seq(erl_syntax:block_expr_body(Node), lay_text_float(","), Ctxt1, fun lay/2),
sep([text("begin"), nest(Ctxt1#ctxt.break_indent, sep(Es)), text("end")]);
'catch_expr' ->
{Prec, PrecR} = preop_prec('catch'),
D = lay(erl_syntax:catch_expr_body(Node), set_prec(Ctxt, PrecR)),
D1 = follow(text("catch"), D, Ctxt#ctxt.break_indent),
maybe_parentheses(D1, Prec, Ctxt);
class_qualifier ->
Ctxt1 = set_prec(Ctxt, max_prec()),
D1 = lay(erl_syntax:class_qualifier_argument(Node), Ctxt1),
D2 = lay(erl_syntax:class_qualifier_body(Node), Ctxt1),
Stacktrace = erl_syntax:class_qualifier_stacktrace(Node),
case erl_syntax:variable_name(Stacktrace) of
'_' -> beside(D1, beside(text(":"), D2));
_ ->
D3 = lay(Stacktrace, Ctxt1),
beside(D1, beside(beside(text(":"), D2), beside(text(":"), D3)))
end;
comment ->
D = stack_comment_lines(erl_syntax:comment_text(Node)),
%% Default padding for standalone comments is empty.
case erl_syntax:comment_padding(Node) of
none -> floating(break(D));
P -> floating(break(beside(text(spaces(P)), D)))
end;
conjunction ->
par(seq(erl_syntax:conjunction_body(Node),
lay_text_float(","),
reset_prec(Ctxt),
fun lay/2));
disjunction ->
%% For clarity, we don't paragraph-format
%% disjunctions; only conjunctions (see above).
sep(seq(erl_syntax:disjunction_body(Node),
lay_text_float(";"),
reset_prec(Ctxt),
fun lay/2));
error_marker ->
E = erl_syntax:error_marker_info(Node),
beside(text("** "), beside(lay_error_info(E, reset_prec(Ctxt)), text(" **")));
eof_marker -> empty();
form_list ->
Es = seq(erl_syntax:form_list_elements(Node), none, reset_prec(Ctxt), fun lay/2),
vertical_sep(text(""), Es);
generator ->
Ctxt1 = reset_prec(Ctxt),
D1 = lay(erl_syntax:generator_pattern(Node), Ctxt1),
D2 = lay(erl_syntax:generator_body(Node), Ctxt1),
par([D1, beside(text("<- "), D2)], Ctxt1#ctxt.break_indent);
binary_generator ->
Ctxt1 = reset_prec(Ctxt),
D1 = lay(erl_syntax:binary_generator_pattern(Node), Ctxt1),
D2 = lay(erl_syntax:binary_generator_body(Node), Ctxt1),
par([D1, beside(text("<= "), D2)], Ctxt1#ctxt.break_indent);
implicit_fun ->
D = lay(erl_syntax:implicit_fun_name(Node), reset_prec(Ctxt)),
beside(lay_text_float("fun "), D);
list_comp ->
Ctxt1 = reset_prec(Ctxt),
D1 = lay(erl_syntax:list_comp_template(Node), Ctxt1),
D2 = par(seq(erl_syntax:list_comp_body(Node), lay_text_float(","), Ctxt1, fun lay/2)),
beside(lay_text_float("["),
par([D1, beside(lay_text_float("|| "), beside(D2, lay_text_float("]")))]));
binary_comp ->
Ctxt1 = set_prec(Ctxt, max_prec()),
D1 = lay(erl_syntax:binary_comp_template(Node), Ctxt1),
D2 = par(seq(erl_syntax:binary_comp_body(Node), lay_text_float(","), Ctxt1, fun lay/2)),
beside(lay_text_float("<< "),
par([D1, beside(lay_text_float(" || "), beside(D2, lay_text_float(" >>")))]));
macro ->
%% This is formatted similar to a normal function call, but
%% prefixed with a "?".
Ctxt1 = reset_prec(Ctxt),
N = erl_syntax:macro_name(Node),
D = case erl_syntax:macro_arguments(Node) of
none -> lay(N, Ctxt1);
Args ->
As = seq(Args, lay_text_float(","), set_prec(Ctxt1, max_prec()), fun lay/2),
beside(lay(N, Ctxt1),
beside(text("("), beside(par(As), lay_text_float(")"))))
end,
D1 = beside(lay_text_float("?"), D),
maybe_parentheses(D1,
0,
Ctxt); % must be conservative!
parentheses ->
D = lay(erl_syntax:parentheses_body(Node), reset_prec(Ctxt)),
lay_parentheses(D);
receive_expr ->
Ctxt1 = reset_prec(Ctxt),
D1 = lay_clauses(erl_syntax:receive_expr_clauses(Node), receive_expr, Ctxt1),
D2 = case erl_syntax:receive_expr_timeout(Node) of
none -> D1;
T ->
D3 = lay(T, Ctxt1),
A = erl_syntax:receive_expr_action(Node),
D4 = sep(seq(A, lay_text_float(","), Ctxt1, fun lay/2)),
sep([D1,
follow(lay_text_float("after"),
append_clause_body(D4, D3, Ctxt1),
Ctxt1#ctxt.break_indent)])
end,
sep([text("receive"), nest(Ctxt1#ctxt.break_indent, D2), text("end")]);
record_access ->
{PrecL, Prec, PrecR} = inop_prec('#'),
D1 = lay(erl_syntax:record_access_argument(Node), set_prec(Ctxt, PrecL)),
D2 = beside(lay_text_float("."),
lay(erl_syntax:record_access_field(Node), set_prec(Ctxt, PrecR))),
T = erl_syntax:record_access_type(Node),
D3 = beside(beside(lay_text_float("#"), lay(T, reset_prec(Ctxt))), D2),
maybe_parentheses(beside(D1, D3), Prec, Ctxt);
record_expr ->
Ctxt1 = reset_prec(Ctxt),
D1 = lay(erl_syntax:record_expr_type(Node), Ctxt1),
D2 = par(seq(erl_syntax:record_expr_fields(Node),
lay_text_float(","),
Ctxt1,
fun lay/2)),
D3 = beside(beside(lay_text_float("#"), D1),
beside(text("{"), beside(D2, lay_text_float("}")))),
Arg = erl_syntax:record_expr_argument(Node),
lay_expr_argument(Arg, D3, Ctxt);
record_field ->
Ctxt1 = reset_prec(Ctxt),
D1 = lay(erl_syntax:record_field_name(Node), Ctxt1),
case erl_syntax:record_field_value(Node) of
none -> D1;
V -> par([D1, lay_text_float("="), lay(V, Ctxt1)], Ctxt1#ctxt.break_indent)
end;
record_index_expr ->
{Prec, PrecR} = preop_prec('#'),
D1 = lay(erl_syntax:record_index_expr_type(Node), reset_prec(Ctxt)),
D2 = lay(erl_syntax:record_index_expr_field(Node), set_prec(Ctxt, PrecR)),
D3 = beside(beside(lay_text_float("#"), D1), beside(lay_text_float("."), D2)),
maybe_parentheses(D3, Prec, Ctxt);
map_expr ->
Ctxt1 = reset_prec(Ctxt),
D1 = par(seq(erl_syntax:map_expr_fields(Node), lay_text_float(","), Ctxt1, fun lay/2)),
D2 = beside(text("#{"), beside(D1, lay_text_float("}"))),
Arg = erl_syntax:map_expr_argument(Node),
lay_expr_argument(Arg, D2, Ctxt);
map_field_assoc ->
Name = erl_syntax:map_field_assoc_name(Node),
Value = erl_syntax:map_field_assoc_value(Node),
lay_type_assoc(Name, Value, Ctxt);
map_field_exact ->
Name = erl_syntax:map_field_exact_name(Node),
Value = erl_syntax:map_field_exact_value(Node),
lay_type_exact(Name, Value, Ctxt);
size_qualifier ->
Ctxt1 = set_prec(Ctxt, max_prec()),
D1 = lay(erl_syntax:size_qualifier_body(Node), Ctxt1),
D2 = lay(erl_syntax:size_qualifier_argument(Node), Ctxt1),
beside(D1, beside(text(":"), D2));
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, lay_text_float("::"), 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), lay_text_float(","), Ctxt1, fun lay/2)),
Es0 = [text("end")],
Es1 = case erl_syntax:try_expr_after(Node) of
[] -> Es0;
As ->
D2 = sep(seq(As, lay_text_float(","), Ctxt1, fun lay/2)),
[text("after"), nest(Ctxt1#ctxt.break_indent, D2) | Es0]
end,
Es2 = case erl_syntax:try_expr_handlers(Node) of
[] -> Es1;
Hs ->
D3 = lay_clauses(Hs, try_expr, Ctxt1),
[text("catch"), nest(Ctxt1#ctxt.break_indent, D3) | Es1]
end,
Es3 = case erl_syntax:try_expr_clauses(Node) of
[] -> Es2;
Cs ->
D4 = lay_clauses(Cs, try_expr, Ctxt1),
[text("of"), nest(Ctxt1#ctxt.break_indent, D4) | Es2]
end,
sep([par([follow(text("try"), D1, Ctxt1#ctxt.break_indent), hd(Es3)]) | tl(Es3)]);
warning_marker ->
E = erl_syntax:warning_marker_info(Node),
beside(text("%% WARNING: "), 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 = lay_follow_beside_text_float(D1, D2, Ctxt),
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, lay_text_float(","), Ctxt1, F),
beside(lay_text_float("<<"), beside(par(D), lay_text_float(">>")));
fun_type -> text("fun()");
constrained_function_type ->
Ctxt1 = reset_prec(Ctxt),
D1 = lay(erl_syntax:constrained_function_type_body(Node), Ctxt1),
Ctxt2 = Ctxt1#ctxt{clause = undefined},
D2 = lay(erl_syntax:constrained_function_type_argument(Node), Ctxt2),
beside(D1, beside(lay_text_float(" when "), D2));
function_type ->
{Before, After} =
case Ctxt#ctxt.clause of
spec -> {"", ""};
_ -> {"fun(", ")"}
end,
Ctxt1 = (reset_prec(Ctxt))#ctxt{clause = undefined},
D1 = case erl_syntax:function_type_arguments(Node) of
any_arity -> text("(...)");
Arguments ->
As = seq(Arguments, lay_text_float(","), Ctxt1, fun lay/2),
beside(text("("), beside(par(As), lay_text_float(")")))
end,
D2 = lay(erl_syntax:function_type_return(Node), Ctxt1),
beside(lay_text_float(Before),
beside(D1, beside(lay_text_float(" -> "), beside(D2, lay_text_float(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 = lay_follow_beside_text_float(D1, D2, Ctxt),
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 ->
Ctxt1 = reset_prec(Ctxt),
Es = seq(Fs, lay_text_float(","), Ctxt1, fun lay/2),
D = beside(lay_text_float("#{"), beside(par(Es), lay_text_float("}"))),
{Prec, _PrecR} = type_preop_prec('#'),
maybe_parentheses(D, Prec, Ctxt)
end;
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 ->
Name = erl_syntax:map_type_exact_name(Node),
Value = erl_syntax:map_type_exact_value(Node),
lay_type_exact(Name, Value, Ctxt);
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),
lay_text_float(","),
reset_prec(Ctxt),
fun lay/2),
D2 = beside(D1, beside(text("{"), beside(par(Es), lay_text_float("}")))),
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, lay_text_float("::"), D2], Ctxt1#ctxt.break_indent);
tuple_type ->
case erl_syntax:tuple_type_elements(Node) of
any_size -> text("tuple()");
Elements ->
Es = seq(Elements, lay_text_float(","), reset_prec(Ctxt), fun lay/2),
beside(lay_text_float("{"), beside(par(Es), lay_text_float("}")))
end;
type_union ->
{_, Prec, PrecR} = type_inop_prec('|'),
Es = sep(seq(erl_syntax:type_union_types(Node),
lay_text_float(" |"),
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.
get_func_node(Node) ->
case erl_syntax:type(Node) of
tuple ->
case erl_syntax:tuple_elements(Node) of
[F0, _] -> F0;
[M0, F0, _] -> erl_syntax:module_qualifier(M0, F0);
_ -> Node
end;
_ -> Node
end.
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_text_float(Str) -> floating(text(Str)).
lay_follow_beside_text_float(D1, D2, Ctxt) ->
follow(beside(D1, lay_text_float(" ::")), D2, Ctxt#ctxt.break_indent).
lay_fun_sep(Clauses, Ctxt) ->
sep([follow(text("fun"), Clauses, Ctxt#ctxt.break_indent), text("end")]).
lay_expr_argument(none, D, Ctxt) ->
{_, Prec, _} = inop_prec('#'),
maybe_parentheses(D, Prec, Ctxt);
lay_expr_argument(Arg, D, Ctxt) ->
{PrecL, Prec, _} = inop_prec('#'),
D1 = beside(lay(Arg, set_prec(Ctxt, PrecL)), D),
maybe_parentheses(D1, Prec, Ctxt).
lay_parentheses(D) -> beside(lay_text_float("("), beside(D, lay_text_float(")"))).
maybe_parentheses(D, Prec, Ctxt) ->
case Ctxt#ctxt.prec of
P when P > Prec -> lay_parentheses(D);
_ -> D
end.
lay_string(S, Ctxt) ->
%% S includes leading/trailing double-quote characters. The segment
%% width is 2/3 of the ribbon width - this seems to work well.
W = Ctxt#ctxt.ribbon * 2 div 3,
lay_string(S, length(S), W).
lay_string(S, L, W) when L > W, W > 0 ->
%% Note that L is the minimum, not the exact, printed length.
case split_string(S, W - 1, L) of
{_S1, ""} -> text(S);
{S1, S2} ->
above(text(S1 ++ "\""),
lay_string([$" | S2], L - W + 1, W)) %" stupid emacs
end;
lay_string(S, _L, _W) -> text(S).
split_string(Xs, N, L) -> split_string_first(Xs, N, L, []).
%% We only split strings at whitespace, if possible. We must make sure
%% we do not split an escape sequence.
split_string_first([$\s | Xs], N, L, As) when N =< 0, L >= 5 ->
{lists:reverse([$\s | As]), Xs};
split_string_first([$\t | Xs], N, L, As) when N =< 0, L >= 5 ->
{lists:reverse([$t, $\\ | As]), Xs};
split_string_first([$\n | Xs], N, L, As) when N =< 0, L >= 5 ->
{lists:reverse([$n, $\\ | As]), Xs};
split_string_first([$\\ | Xs], N, L, As) ->
split_string_second(Xs, N - 1, L - 1, [$\\ | As]);
split_string_first(Xs, N, L, As) when N =< -10, L >= 5 -> {lists:reverse(As), Xs};
split_string_first([_ | _] = S, N, L, As) -> split_string_next(S, N, L, As);
split_string_first([], _N, _L, As) -> {lists:reverse(As), ""}.
split_string_second([$^, X | Xs], N, L, As) ->
split_string_first(Xs, N - 2, L - 2, [X, $^ | As]);
split_string_second([$x, ${ | Xs], N, L, As) ->
split_string_third(Xs, N - 2, L - 2, [${, $x | As]);
split_string_second([X1, X2, X3 | Xs], N, L, As)
when X1 >= $0, X1 =< $7, X2 >= $0, X2 =< $7, X3 >= $0, X3 =< $7 ->
split_string_first(Xs, N - 3, L - 3, [X3, X2, X1 | As]);
split_string_second([X1, X2 | Xs], N, L, As)
when X1 >= $0, X1 =< $7, X2 >= $0, X2 =< $7 ->
split_string_first(Xs, N - 2, L - 2, [X2, X1 | As]);
split_string_second(S, N, L, As) -> split_string_next(S, N, L, As).
split_string_third([$} | Xs], N, L, As) ->
split_string_first(Xs, N - 1, L - 1, [$} | As]);
split_string_third([X | Xs], N, L, As)
when X >= $0, X =< $9; X >= $a, X =< $z; X >= $A, X =< $Z ->
split_string_third(Xs, N - 1, L - 1, [X | As]);
split_string_third([X | _Xs] = S, N, L, As) when X >= $0, X =< $9 ->
split_string_next(S, N, L, As).
split_string_next([X | Xs], N, L, As) -> split_string_first(Xs, N - 1, L - 1, [X | As]);
split_string_next([], N, L, As) -> split_string_first([], N, L, 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.
lay_clauses(Cs, Type, Ctxt) ->
vertical(seq(Cs, lay_text_float(";"), Ctxt#ctxt{clause = Type}, fun lay/2)).
%% Note that for the clause-making functions, the guard argument
%% can be `none', which has different interpretations in different
%% contexts.
make_fun_clause(P, G, B, Ctxt) -> make_fun_clause(none, P, G, B, Ctxt).
make_fun_clause(N, P, G, B, Ctxt) ->
D = make_fun_clause_head(N, P),
make_case_clause(D, G, B, Ctxt).
make_fun_clause_head(none, P) -> lay_parentheses(P);
make_fun_clause_head(N, P) -> beside(N, lay_parentheses(P)).
make_case_clause(P, G, B, Ctxt) -> append_clause_body(B, append_guard(G, P, Ctxt), Ctxt).
make_if_clause(G, B, Ctxt) ->
%% We ignore the patterns; they should be empty anyway.
G1 = case G of
none -> text("true");
_ -> G
end,
append_clause_body(B, G1, Ctxt).
append_clause_body(B, D, Ctxt) -> append_clause_body(B, D, lay_text_float(" ->"), Ctxt).
append_clause_body(B, D, S, Ctxt) -> sep([beside(D, S), nest(Ctxt#ctxt.break_indent, B)]).
append_guard(none, D, _) -> D;
append_guard(G, D, Ctxt) ->
par([D, follow(text("when"), G, Ctxt#ctxt.sub_indent)], Ctxt#ctxt.break_indent).
lay_bit_types([T], Ctxt) -> lay(T, Ctxt);
lay_bit_types([T | Ts], Ctxt) ->
beside(lay(T, Ctxt), beside(lay_text_float("-"), lay_bit_types(Ts, Ctxt))).
lay_error_info({L, M, T} = T0, Ctxt) when is_integer(L), is_atom(M) ->
case catch apply(M, format_error, [T]) of
S when is_list(S) ->
case L > 0 of
true -> beside(text(io_lib:format("~w: ", [L])), text(S));
_ -> text(S)
end;
_ -> lay_concrete(T0, Ctxt)
end;
lay_error_info(T, Ctxt) -> lay_concrete(T, Ctxt).
lay_concrete(T, Ctxt) -> lay(erl_syntax:abstract(T), Ctxt).
lay_type_assoc(Name, Value, Ctxt) -> lay_type_par_text(Name, Value, "=>", Ctxt).
lay_type_exact(Name, Value, Ctxt) -> lay_type_par_text(Name, Value, ":=", Ctxt).
lay_type_par_text(Name, Value, Text, Ctxt) ->
Ctxt1 = reset_prec(Ctxt),
D1 = lay(Name, Ctxt1),
D2 = lay(Value, Ctxt1),
par([D1, lay_text_float(Text), D2], Ctxt1#ctxt.break_indent).
lay_type_application(Name, Arguments, Ctxt) ->
{PrecL, Prec} = func_prec(), %
D1 = lay(Name, set_prec(Ctxt, PrecL)),
As = seq(Arguments, lay_text_float(","), reset_prec(Ctxt), fun lay/2),
D = beside(D1, beside(text("("), beside(par(As), lay_text_float(")")))),
maybe_parentheses(D, Prec, Ctxt).
seq([H | T], Separator, Ctxt, Fun) ->
case T of
[] -> [Fun(H, Ctxt)];
_ -> [maybe_append(Separator, Fun(H, Ctxt)) | seq(T, Separator, Ctxt, Fun)]
end;
seq([], _, _, _) -> [empty()].
maybe_append(none, D) -> D;
maybe_append(Suffix, D) -> beside(D, Suffix).
vertical([D]) -> D;
vertical([D | Ds]) -> above(D, vertical(Ds));
vertical([]) -> [].
vertical_sep(_Sep, [D]) -> D;
vertical_sep(Sep, [D | Ds]) -> above(above(D, Sep), vertical_sep(Sep, Ds));
vertical_sep(_Sep, []) -> [].
spaces(N) when N > 0 -> [$\s | spaces(N - 1)];
spaces(_) -> [].
tidy_integer(Node) -> tidy_number(Node, erl_syntax:integer_literal(Node)).
tidy_float(Node) ->
tidy_number(Node, io_lib:format("~p", [erl_syntax:float_value(Node)])).
%% @doc If we captured the original text for the number, then we use it.
%% Otherwise, we use the value returned by the parser.
%% The goal is to preserve things like 16#FADE or -1e-1 instead of turning
%% them into integers or "pretty printed" floats.
tidy_number(Node, Default) ->
number_from_text(erl_anno:text(
erl_syntax:get_pos(Node)),
Default).
%% @doc This function covers the corner case when erl_parse:parse_form/1
%% (used by ktn_dodger) screws up the text for things like fun x/1 or
%% -vsn(1) and therefore that text, that was actually captured,
%% can not be used.
%% NOTE: floats work as "integers" according to string:to_integer/1
number_from_text(undefined, Default) -> Default;
number_from_text(Text, Default) ->
case string:to_integer(Text) of
{error, no_integer} -> Default;
{_, _} -> Text
end.
lay_clause_expressions([H], Ctxt) -> lay(H, Ctxt);
lay_clause_expressions([H | T], Ctxt) ->
Clause = beside(lay(H, Ctxt), lay_text_float(",")),
Next = lay_clause_expressions(T, Ctxt),
case is_last_and_before_empty_line(H, T, Ctxt) of
true -> above(above(Clause, text("")), Next);
false -> above(Clause, Next)
end;
lay_clause_expressions([], _) -> empty().
is_last_and_before_empty_line(H, [H2 | _], #ctxt{empty_lines = EmptyLines}) ->
try
(get_line(H2) - get_line(H) >= 2) and sets:is_element(get_line(H) + 1, EmptyLines)
catch
error:badarith -> false
end.
get_line(Tree) ->
Anno = erl_syntax:get_pos(Tree),
erl_anno:line(Anno).
%% =====================================================================