Current section

Files

Jump to
erlfmt src erlfmt_format.erl
Raw

src/erlfmt_format.erl

%% Copyright (c) Facebook, Inc. and its affiliates.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
-module(erlfmt_format).
-include("erlfmt.hrl").
-export([expr_to_algebra/1, form_to_algebra/1]).
-import(erlfmt_algebra, [
document_text/1,
document_spaces/1,
document_combine/2,
document_flush/1,
document_choice/2,
document_single_line/1,
document_prepend/2,
document_reduce/2,
document_fail/0
]).
-define(INDENT, 4).
-define(NEXT_BREAK_FITS, [map, list, record, block, 'fun']).
-define(NEXT_BREAK_FITS_OPS, ['=', '::']).
-spec form_to_algebra(erlfmt_parse:abstract_form()) -> erlfmt_algebra:document().
form_to_algebra({function, Meta, Clauses}) ->
Doc = document_combine(clauses_to_algebra(Clauses), document_text(".")),
combine_comments(Meta, Doc);
form_to_algebra({attribute, Meta, Name, []}) ->
Doc = wrap(document_text("-"), expr_to_algebra(Name), document_text(".")),
combine_comments(Meta, Doc);
form_to_algebra({attribute, Meta, {atom, _, define}, [Define | Body]}) ->
HeadD = document_combine(document_text("-define("), expr_to_algebra(Define)),
BodyD = block_to_algebra(Body),
EndD = document_text(")."),
case next_break_fits(hd(Body)) of
true ->
Doc = prepend_comma_space(HeadD, document_combine(BodyD, EndD)),
combine_comments(Meta, Doc);
false ->
SingleBody = document_combine(document_single_line(BodyD), EndD),
Doc = document_choice(
combine_comma_space(HeadD, SingleBody),
wrap_nested(document_combine(HeadD, document_text(",")), BodyD, EndD)
),
combine_comments(Meta, Doc)
end;
form_to_algebra({attribute, Meta, {atom, _, RawName} = Name, [Value]})
when RawName =:= type; RawName =:= opaque; RawName =:= spec; RawName =:= callback ->
NameD = wrap(document_text("-"), expr_to_algebra(Name), document_text(" ")),
ValueD = expr_to_algebra(Value),
Doc = wrap_prepend(NameD, ValueD, document_text(".")),
combine_comments(Meta, Doc);
form_to_algebra({attribute, Meta, {atom, _, record}, [Name, {tuple, TMeta, Values}]}) ->
HeadD = wrap(document_text("-record("), expr_to_algebra(Name), document_text(",")),
case no_comments_or_parens(TMeta) of
true ->
Prefix = document_combine(HeadD, document_text(" {")),
Doc = container_to_algebra(TMeta, Values, Prefix, document_text("}).")),
combine_comments(Meta, Doc);
false ->
Doc = combine_newline(
combine_nested(HeadD, expr_to_algebra({tuple, TMeta, Values})),
document_text(").")
),
combine_comments(Meta, Doc)
end;
form_to_algebra({attribute, Meta, Name, Values}) ->
Prefix = wrap(document_text("-"), expr_to_algebra(Name), document_text("(")),
Doc = container_to_algebra(Meta, Values, Prefix, document_text(").")),
combine_comments(Meta, Doc).
-spec expr_to_algebra(erlfmt_parse:abstract_expr()) -> erlfmt_algebra:document().
expr_to_algebra(Expr) when is_tuple(Expr) ->
Meta = element(2, Expr),
Doc = do_expr_to_algebra(Expr),
combine_comments(Meta, maybe_wrap_in_parens(Meta, Doc));
expr_to_algebra(Other) ->
do_expr_to_algebra(Other).
do_expr_to_algebra({string, Meta, _Value}) ->
string_to_algebra(erlfmt_scan:get_anno(text, Meta));
do_expr_to_algebra({char, #{text := "$ "}, $\s}) ->
document_text("$\\s");
do_expr_to_algebra({Atomic, Meta, _Value}) when ?IS_ATOMIC(Atomic) ->
document_text(erlfmt_scan:get_anno(text, Meta));
do_expr_to_algebra({concat, _Meta, Values}) ->
concat_to_algebra(Values);
do_expr_to_algebra({op, _Meta, Op, Expr}) ->
unary_op_to_algebra(Op, Expr);
do_expr_to_algebra({op, Meta, Op, Left, Right}) ->
binary_op_to_algebra(Op, Meta, Left, Right);
do_expr_to_algebra({tuple, Meta, Values}) ->
fill_container_to_algebra(Meta, Values, document_text("{"), document_text("}"));
do_expr_to_algebra({list, Meta, Values}) ->
container_to_algebra(Meta, Values, document_text("["), document_text("]"));
do_expr_to_algebra({cons, _, Head, Tail}) ->
cons_to_algebra(Head, Tail);
do_expr_to_algebra({bin, Meta, Values}) ->
fill_container_to_algebra(Meta, Values, document_text("<<"), document_text(">>"));
do_expr_to_algebra({bin_element, _Meta, Expr, Size, Types}) ->
bin_element_to_algebra(Expr, Size, Types);
do_expr_to_algebra({map, Meta, Values}) ->
container_to_algebra(Meta, Values, document_text("#{"), document_text("}"));
do_expr_to_algebra({map, Meta, Expr, Values}) ->
Prefix = document_combine(expr_to_algebra(Expr), document_text("#{")),
container_to_algebra(Meta, Values, Prefix, document_text("}"));
do_expr_to_algebra({map_field_assoc, _Meta, Key, Value}) ->
field_to_algebra("=>", Key, Value);
do_expr_to_algebra({map_field_exact, _Meta, Key, Value}) ->
field_to_algebra(":=", Key, Value);
do_expr_to_algebra({record, Meta, Name, Values}) ->
Prefix = document_combine(record_name_to_algebra(Meta, Name), document_text("{")),
container_to_algebra(Meta, Values, Prefix, document_text("}"));
do_expr_to_algebra({record, Meta, Expr, Name, Values}) ->
PrefixName = document_combine(record_name_to_algebra(Meta, Name), document_text("{")),
Prefix = document_combine(expr_to_algebra(Expr), PrefixName),
container_to_algebra(Meta, Values, Prefix, document_text("}"));
do_expr_to_algebra({record_field, _Meta, Key, Value}) ->
field_to_algebra("=", Key, Value);
do_expr_to_algebra({record_index, Meta, Name, Key}) ->
record_access_to_algebra(Meta, Name, Key);
do_expr_to_algebra({record_field, _Meta, Name}) ->
expr_to_algebra(Name);
do_expr_to_algebra({record_field, Meta, Expr, Name, Key}) ->
Access = record_access_to_algebra(Meta, Name, Key),
document_combine(expr_to_algebra(Expr), Access);
do_expr_to_algebra({lc, _Meta, Expr, LcExprs}) ->
ExprD = expr_to_algebra(Expr),
comprehension_to_algebra(ExprD, LcExprs, document_text("["), document_text("]"));
do_expr_to_algebra({bc, _Meta, Expr, LcExprs}) ->
ExprD = expr_to_algebra(Expr),
comprehension_to_algebra(ExprD, LcExprs, document_text("<<"), document_text(">>"));
do_expr_to_algebra({generate, _Meta, Left, Right}) ->
field_to_algebra("<-", Left, Right);
do_expr_to_algebra({b_generate, _Meta, Left, Right}) ->
field_to_algebra("<=", Left, Right);
do_expr_to_algebra({call, Meta, Name, Args}) ->
Prefix = document_combine(expr_to_algebra(Name), document_text("(")),
container_to_algebra(Meta, Args, Prefix, document_text(")"));
do_expr_to_algebra({macro_call, _Meta, Name, none}) ->
document_combine(document_text("?"), expr_to_algebra(Name));
do_expr_to_algebra({macro_call, Meta, Name, Args}) ->
Prefix = wrap(document_text("?"), expr_to_algebra(Name), document_text("(")),
container_to_algebra(Meta, Args, Prefix, document_text(")"));
do_expr_to_algebra({macro_string, _Meta, Name}) ->
document_combine(document_text("??"), expr_to_algebra(Name));
do_expr_to_algebra({remote, _Meta, Left, Right}) ->
combine_sep(expr_to_algebra(Left), ":", expr_to_algebra(Right));
do_expr_to_algebra({block, _Meta, Exprs}) ->
wrap_nested(document_text("begin"), block_to_algebra(Exprs), document_text("end"));
do_expr_to_algebra({'fun', _Meta, Expr}) ->
fun_to_algebra(Expr);
do_expr_to_algebra({'case', _Meta, Expr, Clauses}) ->
Prefix = wrap(document_text("case "), expr_to_algebra(Expr), document_text(" of")),
wrap_nested(Prefix, clauses_to_algebra(Clauses), document_text("end"));
do_expr_to_algebra({'receive', _Meta, Clauses}) ->
wrap_nested(
document_text("receive"),
clauses_to_algebra(Clauses),
document_text("end")
);
do_expr_to_algebra({'receive', _Meta, [], AfterExpr, AfterBody}) ->
AfterD = receive_after_to_algebra(AfterExpr, AfterBody),
combine_newline(
document_text("receive"),
combine_newline(AfterD, document_text("end"))
);
do_expr_to_algebra({'receive', _Meta, Clauses, AfterExpr, AfterBody}) ->
AfterD = receive_after_to_algebra(AfterExpr, AfterBody),
Suffix = combine_newline(AfterD, document_text("end")),
wrap_nested(document_text("receive"), clauses_to_algebra(Clauses), Suffix);
do_expr_to_algebra({'try', _Meta, Exprs, OfClauses, CatchClauses, After}) ->
try_to_algebra(Exprs, OfClauses, CatchClauses, After);
do_expr_to_algebra({'if', _Meta, Clauses}) ->
wrap_nested(document_text("if"), clauses_to_algebra(Clauses), document_text("end"));
do_expr_to_algebra({spec, _Meta, Name, [SingleClause]}) ->
single_clause_spec_to_algebra(Name, SingleClause);
do_expr_to_algebra({spec, _Meta, Name, Clauses}) ->
combine_nested(expr_to_algebra(Name), clauses_to_algebra(Clauses));
do_expr_to_algebra({'...', _Meta}) ->
document_text("...");
do_expr_to_algebra({bin_size, _Meta, Left, Right}) ->
wrap(expr_to_algebra(Left), document_text("*"), expr_to_algebra(Right));
do_expr_to_algebra({guard_or, _Meta, Guards}) ->
element(2, do_guard_or_to_algebra_pair(Guards, "")).
combine_space(D1, D2) -> combine_sep(D1, " ", D2).
combine_comma_space(D1, D2) -> combine_sep(D1, ", ", D2).
combine_dash(D1, D2) -> combine_sep(D1, "-", D2).
combine_semi_space(D1, D2) -> combine_sep(D1, "; ", D2).
combine_colon(D1, D2) -> combine_sep(D1, ":", D2).
combine_sep(D1, Sep, D2) ->
document_combine(D1, document_combine(document_text(Sep), D2)).
combine_newline(D1, D2) ->
document_combine(document_flush(D1), D2).
combine_comma_newline(D1, D2) ->
Left = document_flush(document_combine(D1, document_text(","))),
document_combine(Left, D2).
combine_comma_double_newline(D1, D2) ->
Left = document_flush(document_flush(document_combine(D1, document_text(",")))),
document_combine(Left, D2).
combine_semi_newline(D1, D2) ->
document_combine(document_flush(document_combine(D1, document_text(";"))), D2).
combine_all(Docs) ->
document_reduce(fun erlfmt_algebra:document_combine/2, Docs).
combine_nested(Head, Doc) -> combine_nested(Head, Doc, ?INDENT).
combine_nested(Head, Doc, Indent) ->
combine_newline(Head, document_combine(document_spaces(Indent), Doc)).
prepend_space(D1, D2) ->
wrap_prepend(D1, document_text(" "), D2).
prepend_comma_space(D1, D2) ->
wrap_prepend(D1, document_text(", "), D2).
wrap(Left, Doc, Right) ->
document_combine(Left, document_combine(Doc, Right)).
wrap_prepend(Left, Doc, Right) ->
document_prepend(Left, document_prepend(Doc, Right)).
wrap_in_parens(Doc) -> wrap_prepend(document_text("("), Doc, document_text(")")).
wrap_nested(Left, Doc, Right) ->
Nested = document_combine(document_spaces(?INDENT), Doc),
combine_newline(Left, combine_newline(Nested, Right)).
string_to_algebra(Text) ->
case string:split(Text, "\n", all) of
[Line] ->
document_text(Line);
[First, "\""] ->
document_text([First, "\\n\""]);
[First | Lines] ->
FirstD = document_text([First | "\\n\""]),
LinesD = string_lines_to_algebra(Lines),
combine_newline(FirstD, LinesD)
end.
string_lines_to_algebra([LastLine]) ->
document_text(["\"" | LastLine]);
string_lines_to_algebra([Line, "\""]) ->
document_text(["\"", Line | "\\n\""]);
string_lines_to_algebra([Line | Lines]) ->
LineD = document_text(["\"", Line | "\\n\""]),
combine_newline(LineD, string_lines_to_algebra(Lines)).
%% there's always at least two elements in concat
concat_to_algebra([Value1, Value2 | _] = Values) ->
ValuesD = lists:map(fun expr_to_algebra/1, Values),
Vertical = document_reduce(fun combine_newline/2, ValuesD),
case break_between(Value1, Value2) of
true ->
Vertical;
false ->
Horizontal = document_reduce(fun combine_space/2, ValuesD),
document_choice(Horizontal, Vertical)
end.
unary_op_to_algebra(Op, Expr) ->
OpD = document_text(atom_to_binary(Op, utf8)),
ExprD = expr_to_algebra(Expr),
NeedsSpace = unary_needs_space(Expr, Op),
if
NeedsSpace ->
combine_space(OpD, ExprD);
true ->
document_combine(OpD, ExprD)
end.
unary_needs_space(_, Op) when Op =:= 'not'; Op =:= 'bnot'; Op =:= 'catch' ->
true;
unary_needs_space({op, Meta, _, _}, _) ->
not erlfmt_scan:get_anno(parens, Meta, false);
unary_needs_space(_, _) ->
false.
binary_op_to_algebra('..', _Meta, Left, Right) ->
%% .. is special - non-assoc, no spaces around and never breaks
wrap(expr_to_algebra(Left), document_text(".."), expr_to_algebra(Right));
binary_op_to_algebra('/', _Meta, {atom, _, _} = Left, {integer, _, _} = Right) ->
%% special-case the foo/N syntax in attributes, division with a literal atom
%% doesn't make sense in normal code, so it's safe to apply it everywhere
wrap(expr_to_algebra(Left), document_text("/"), expr_to_algebra(Right));
binary_op_to_algebra('|' = Op, Meta0, Left, Right) ->
%% | does not increase indentation
%% don't print parens and comments twice - expr_to_algebra took care of it already
Meta = erlfmt_scan:delete_annos([parens, pre_comments, post_comments], Meta0),
binary_op_to_algebra(Op, Meta, Left, Right, 0);
binary_op_to_algebra(Op, Meta0, Left, Right) ->
%% don't print parens and comments twice - expr_to_algebra took care of it already
Meta = erlfmt_scan:delete_annos([parens, pre_comments, post_comments], Meta0),
binary_op_to_algebra(Op, Meta, Left, Right, ?INDENT).
binary_op_to_algebra(Op, Meta, Left, Right, Indent) ->
OpD = document_text(atom_to_binary(Op, utf8)),
LeftD = binary_operand_to_algebra(Op, Left, Indent),
RightD = binary_operand_to_algebra(Op, Right, 0),
LeftOpD = combine_space(LeftD, OpD),
SingleD =
case {next_break_fits_op(Op, Right), break_between(Left, Right)} of
{true, _} -> prepend_space(LeftOpD, RightD);
{false, true} -> document_fail();
{false, false} -> combine_space(LeftOpD, document_single_line(RightD))
end,
Doc = document_choice(
SingleD,
combine_newline(LeftOpD, document_combine(document_spaces(Indent), RightD))
),
combine_comments(Meta, maybe_wrap_in_parens(Meta, Doc)).
binary_operand_to_algebra(Op, {op, Meta, Op, Left, Right}, Indent) ->
%% Same operator, no parens, means correct side and no repeated nesting
case erlfmt_scan:get_anno(parens, Meta, false) of
false -> binary_op_to_algebra(Op, Meta, Left, Right, Indent);
_ -> binary_op_to_algebra(Op, Meta, Left, Right, ?INDENT)
end;
binary_operand_to_algebra(_ParentOp, Expr, _Indent) ->
expr_to_algebra(Expr).
fill_container_to_algebra(_Meta, [], Left, Right) ->
document_combine(Left, Right);
fill_container_to_algebra(#{newline := true}, Values, Left, Right) ->
VerticalD = container_vertical_values_to_algebra(Values),
wrap_nested(Left, VerticalD, Right);
fill_container_to_algebra(_Meta, [Value], Left, Right) ->
wrap_prepend(Left, expr_to_algebra(Value), Right);
fill_container_to_algebra(_Meta, [Value | Values], Left, Right) ->
ValueD = document_combine(expr_to_algebra(Value), document_text(", ")),
Doc = fill_container_values_to_algebra(ValueD, Values),
wrap_prepend(Left, Doc, Right).
fill_container_values_to_algebra(Acc0, [Expr]) ->
ExprD = expr_to_algebra(Expr),
Doc = document_choice(
document_combine(Acc0, document_single_line(ExprD)),
combine_nested(Acc0, ExprD)
),
case next_break_fits(Expr) of
true ->
document_choice(document_prepend(document_single_line(Acc0), ExprD), Doc);
false ->
Doc
end;
fill_container_values_to_algebra(Acc0, [Expr | Rest]) ->
ExprD = document_combine(expr_to_algebra(Expr), document_text(", ")),
SingleExprD = document_single_line(ExprD),
Acc = document_choice(
document_combine(Acc0, SingleExprD),
document_choice(
combine_nested(Acc0, SingleExprD),
combine_nested(Acc0, document_flush(ExprD))
)
),
fill_container_values_to_algebra(Acc, Rest).
container_to_algebra(_Meta, [], Left, Right) ->
document_combine(Left, Right);
container_to_algebra(Meta, Values, Left, Right) ->
{Single, Multi} = container_to_algebra_pair(Meta, Values, Left, Right),
document_choice(Single, Multi).
container_to_algebra_pair(_, [], Left, Right) ->
Doc = document_combine(Left, Right),
{Doc, Doc};
container_to_algebra_pair(#{newline := true}, Values, Left, Right) ->
VerticalD = container_vertical_values_to_algebra(Values),
{document_fail(), wrap_nested(Left, VerticalD, Right)};
container_to_algebra_pair(_, Values, Left, Right) ->
{Horizontal, LastFits, Vertical} = container_values_to_algebra_pair(Values),
HorizontalD = wrap(Left, Horizontal, Right),
VerticalD = document_choice(
wrap_prepend(Left, LastFits, Right),
wrap_nested(Left, Vertical, Right)
),
{HorizontalD, VerticalD}.
%% standalone comments are always trailing, but can appear as only expressions
container_values_to_algebra_pair([{comment, _, _} | _] = Comments) ->
{document_fail(), document_fail(), comments_to_algebra(Comments)};
container_values_to_algebra_pair([Expr | [{comment, _, _} | _] = Comments]) ->
CommentsD = comments_to_algebra(Comments),
ExprD = expr_to_algebra(Expr),
{document_fail(), document_fail(), combine_newline(ExprD, CommentsD)};
container_values_to_algebra_pair([Expr]) ->
ExprD = expr_to_algebra(Expr),
case next_break_fits(Expr) of
true -> {document_single_line(ExprD), ExprD, ExprD};
false -> {document_single_line(ExprD), document_fail(), ExprD}
end;
container_values_to_algebra_pair([Expr | Rest]) ->
ExprD = expr_to_algebra(Expr),
SingleExprD = document_single_line(ExprD),
{RestSingle, RestLastFits, RestMulti} = container_values_to_algebra_pair(Rest),
Single = combine_comma_space(SingleExprD, RestSingle),
LastFits = prepend_comma_space(SingleExprD, RestLastFits),
Multi = combine_comma_newline(ExprD, RestMulti),
{Single, LastFits, Multi}.
container_vertical_values_to_algebra([{comment, _, _} | _] = Comments) ->
comments_to_algebra(Comments);
container_vertical_values_to_algebra([Expr | [{comment, _, _} | _] = Comments]) ->
combine_newline(expr_to_algebra(Expr), comments_to_algebra(Comments));
container_vertical_values_to_algebra([Expr]) ->
expr_to_algebra(Expr);
container_vertical_values_to_algebra([Expr | Rest]) ->
combine_comma_newline(
expr_to_algebra(Expr),
container_vertical_values_to_algebra(Rest)
).
cons_to_algebra(Head, Tail) ->
HeadD = expr_to_algebra(Head),
TailD = document_combine(document_text("| "), expr_to_algebra(Tail)),
document_choice(
combine_space(document_single_line(HeadD), document_single_line(TailD)),
combine_newline(HeadD, TailD)
).
bin_element_to_algebra(Expr, Size, Types) ->
Docs =
[expr_to_algebra(Expr)] ++
[bin_size_to_algebra(Size) || Size =/= default] ++
[bin_types_to_algebra(Types) || Types =/= default],
document_reduce(fun erlfmt_algebra:document_combine/2, Docs).
bin_size_to_algebra(Expr) ->
document_combine(document_text(":"), expr_to_algebra(Expr)).
bin_types_to_algebra(Types) ->
TypesD = lists:map(fun expr_to_algebra/1, Types),
document_combine(document_text("/"), document_reduce(fun combine_dash/2, TypesD)).
record_access_to_algebra(Meta, Name, Key) ->
NameD = record_name_to_algebra(Meta, Name),
KeyD = expr_to_algebra(Key),
DotD = document_text("."),
document_combine(NameD, document_combine(DotD, KeyD)).
record_name_to_algebra(Meta, Name) ->
%% Differentiate between #?FOO{} and ?FOO{}
case erlfmt_scan:get_anno(macro_record, Meta, false) of
true -> expr_to_algebra(Name);
false -> document_combine(document_text("#"), expr_to_algebra(Name))
end.
field_to_algebra(Op, Key, Value) ->
KeyD = expr_to_algebra(Key),
ValueD = expr_to_algebra(Value),
KeyOpD = combine_space(KeyD, document_text(Op)),
case next_break_fits(Value, [call, macro_call]) of
true ->
document_choice(
prepend_space(KeyOpD, ValueD),
combine_nested(KeyOpD, ValueD)
);
false ->
document_choice(
combine_space(KeyOpD, document_single_line(ValueD)),
combine_nested(KeyOpD, ValueD)
)
end.
comprehension_to_algebra(ExprD, LcExprs, Left, Right) ->
PipesD = document_text("|| "),
{LcExprsSingleD, _LcExprsLastFitsD, LcExprsMultiD} =
container_values_to_algebra_pair(LcExprs),
LcExprsD = document_choice(LcExprsSingleD, LcExprsMultiD),
SingleLine = combine_space(
document_single_line(ExprD),
document_combine(PipesD, LcExprsSingleD)
),
Multiline = document_choice(
combine_space(ExprD, document_combine(PipesD, LcExprsSingleD)),
combine_newline(ExprD, document_combine(PipesD, LcExprsD))
),
document_choice(
wrap(Left, SingleLine, Right),
wrap_nested(Left, Multiline, Right)
).
%% standalone comments are always trailing other expressions
block_to_algebra([Expr | [{comment, _, _} | _] = Comments]) ->
combine_newline(expr_to_algebra(Expr), comments_to_algebra(Comments));
block_to_algebra([Expr]) ->
expr_to_algebra(Expr);
block_to_algebra([Expr | [Next | _] = Rest]) ->
ExprD = expr_to_algebra(Expr),
RestD = block_to_algebra(Rest),
case erlfmt_scan:get_end_line(Expr) + 1 < erlfmt_scan:get_line(Next) of
true -> combine_comma_double_newline(ExprD, RestD);
false -> combine_comma_newline(ExprD, RestD)
end.
fun_to_algebra({function, _Anno, Name, Arity}) ->
combine_all([
document_text("fun "),
expr_to_algebra(Name),
document_text("/"),
expr_to_algebra(Arity)
]);
fun_to_algebra({function, _Anno, Mod, Name, Arity}) ->
combine_all([
document_text("fun "),
expr_to_algebra(Mod),
document_text(":"),
expr_to_algebra(Name),
document_text("/"),
expr_to_algebra(Arity)
]);
fun_to_algebra({clauses, _Anno, [Clause]}) ->
FunD = document_text("fun "),
{Single, Multi} = clause_to_algebra_pair(Clause),
MultiD = combine_newline(document_prepend(FunD, Multi), document_text("end")),
case erlfmt_scan:get_anno(newline, Clause, false) of
true -> MultiD;
false -> document_choice(wrap(FunD, Single, document_text(" end")), MultiD)
end;
fun_to_algebra({clauses, _Anno, Clauses}) ->
ClausesD = clauses_to_algebra(Clauses),
document_choice(
wrap(document_text("fun "), document_single_line(ClausesD), document_text(" end")),
wrap_nested(document_text("fun"), ClausesD, document_text("end"))
);
fun_to_algebra(type) ->
document_text("fun()");
fun_to_algebra({type, Anno, Args, Result}) ->
ResultD = expr_to_algebra(Result),
ArgsD = container_to_algebra(Anno, Args, document_text("("), document_text(") ->")),
SingleLine =
case next_break_fits(Result) of
true -> prepend_space(ArgsD, ResultD);
false -> combine_space(ArgsD, document_single_line(ResultD))
end,
Body = document_choice(SingleLine, combine_nested(ArgsD, ResultD)),
wrap(document_text("fun("), Body, document_text(")")).
clauses_to_algebra([{_, #{newline := true}, _, _, _} | _] = Clauses) ->
{_Single, Multi} = clauses_to_algebra_pair(Clauses),
Multi;
clauses_to_algebra(Clauses) ->
{Single, Multi} = clauses_to_algebra_pair(Clauses),
document_choice(Single, Multi).
%% standalone comments are always trailing other expressions
clauses_to_algebra_pair([Clause | [{comment, _, _} | _] = Comments]) ->
CommentsD = comments_to_algebra(Comments),
{SingleClauseD, MultiClauseD} = clause_to_algebra_pair(Clause),
{combine_newline(SingleClauseD, CommentsD), combine_newline(MultiClauseD, CommentsD)};
clauses_to_algebra_pair([Clause]) ->
clause_to_algebra_pair(Clause);
clauses_to_algebra_pair([Clause | Rest]) ->
{SingleClauseD, MultiClauseD} = clause_to_algebra_pair(Clause),
{RestSingle, RestMulti} = clauses_to_algebra_pair(Rest),
Single = combine_semi_newline(SingleClauseD, RestSingle),
Multi = combine_semi_newline(MultiClauseD, RestMulti),
{Single, Multi}.
clause_to_algebra_pair({Clause, Meta, _, _, _} = Node)
when Clause =:= clause; Clause =:= spec_clause ->
%% clause nodes only have precomments
{Pre, []} = comments(Meta),
{Single, Multi} = do_clause_to_algebra_pair(Node),
{combine_pre_comments(Pre, Single), combine_pre_comments(Pre, Multi)};
clause_to_algebra_pair({macro_call, _, _, _} = Expr) ->
%% It's possible the entire clause is defined inside of a macro call
ExprD = expr_to_algebra(Expr),
{document_single_line(ExprD), ExprD}.
do_clause_to_algebra_pair({clause, _Meta, empty, Guards, Body}) ->
BodyD = block_to_algebra(Body),
SingleBodyD = document_single_line(BodyD),
{SingleGuardsD, _InlineGuardsD, GuardsD} = guard_or_to_algebra_pair(Guards, ""),
SingleD = wrap(SingleGuardsD, document_text(" -> "), SingleBodyD),
MultiD = combine_nested(document_combine(GuardsD, document_text(" ->")), BodyD),
{SingleD, MultiD};
%% If there are no guards, spec is the same as regular clauses
do_clause_to_algebra_pair({spec_clause, Meta, Head, Body, empty}) ->
do_clause_to_algebra_pair({clause, Meta, Head, empty, Body});
do_clause_to_algebra_pair({spec_clause, _Meta, Head, [Body], Guards}) ->
{SingleHeadD, HeadD} = clause_head_to_algebra(Head),
{SingleGuardsD, _InlineGuardsD, GuardsD} = guard_or_to_algebra_pair(Guards, "when "),
BodyD = expr_to_algebra(Body),
SingleBodyD = document_single_line(BodyD),
SingleD =
wrap(SingleHeadD, document_text(" -> "), combine_space(SingleBodyD, SingleGuardsD)),
MultiPrefix = document_choice(
wrap(SingleHeadD, document_text(" -> "), SingleBodyD),
document_choice(
combine_nested(document_combine(SingleHeadD, document_text(" ->")), BodyD),
wrap(HeadD, document_text(" -> "), BodyD)
)
),
MultiD = combine_nested(MultiPrefix, GuardsD),
{SingleD, MultiD};
do_clause_to_algebra_pair({clause, _Meta, Head, empty, Body}) ->
{SingleHeadD, HeadD} = clause_head_to_algebra(Head),
BodyD = block_to_algebra(Body),
SingleBodyD = document_single_line(BodyD),
SingleD = wrap(SingleHeadD, document_text(" -> "), SingleBodyD),
MultiPrefix =
document_combine(document_choice(SingleHeadD, HeadD), document_text(" ->")),
MultiD = combine_nested(MultiPrefix, BodyD),
{SingleD, MultiD};
do_clause_to_algebra_pair({clause, _Meta, Head, Guards, Body}) ->
{SingleHeadD, HeadD} = clause_head_to_algebra(Head),
{SingleGuardsD, InlineGuardsD, GuardsD} = guard_or_to_algebra_pair(Guards, "when "),
BodyD = block_to_algebra(Body),
SingleBodyD = document_single_line(BodyD),
SingleD =
combine_space(SingleHeadD, wrap(SingleGuardsD, document_text(" -> "), SingleBodyD)),
MultiPrefix = document_choice(
combine_space(document_choice(SingleHeadD, HeadD), InlineGuardsD),
combine_nested(SingleHeadD, GuardsD, ?INDENT * 2)
),
MultiD = combine_nested(document_combine(MultiPrefix, document_text(" ->")), BodyD),
{SingleD, MultiD}.
clause_head_to_algebra({args, Meta, Args}) ->
container_to_algebra_pair(Meta, Args, document_text("("), document_text(")"));
clause_head_to_algebra({'catch', _, Args}) ->
ArgsD = lists:map(fun expr_to_algebra/1, Args),
Doc = document_reduce(fun combine_colon/2, ArgsD),
{document_single_line(Doc), Doc};
clause_head_to_algebra({call, Meta, Name, Args}) ->
Prefix = document_combine(expr_to_algebra(Name), document_text("(")),
container_to_algebra_pair(Meta, Args, Prefix, document_text(")"));
clause_head_to_algebra(Expr) ->
ExprD = expr_to_algebra(Expr),
{document_single_line(ExprD), ExprD}.
guard_or_to_algebra_pair({guard_or, Meta, Guards}, When) ->
case comments(Meta) of
{[], []} ->
{Single, Multi} = do_guard_or_to_algebra_pair(Guards, When),
{Single, Multi, Multi};
{Pre, Post} ->
{_Single, Multi} = do_guard_or_to_algebra_pair(Guards, When),
Doc = combine_pre_comments(Pre, combine_post_comments(Post, Multi)),
{document_fail(), document_fail(), Doc}
end.
do_guard_or_to_algebra_pair(Guards, When) ->
WhenD = document_text(When),
{SingleLine, MultiLine} =
lists:unzip(lists:map(fun guard_and_to_algebra_pair/1, Guards)),
SingleLineD = document_reduce(fun combine_semi_space/2, SingleLine),
MultiLineD = document_reduce(fun combine_semi_newline/2, MultiLine),
{
document_combine(WhenD, SingleLineD),
document_combine(WhenD, document_choice(SingleLineD, MultiLineD))
}.
guard_and_to_algebra_pair({guard_and, Meta, Exprs}) ->
ExprsD = lists:map(fun expr_to_algebra/1, Exprs),
case comments(Meta) of
{[], []} ->
SingleLine = lists:map(fun erlfmt_algebra:document_single_line/1, ExprsD),
SingleLineD = document_reduce(fun combine_comma_space/2, SingleLine),
MultiLineD = document_reduce(fun combine_comma_newline/2, ExprsD),
{SingleLineD, document_choice(SingleLineD, MultiLineD)};
{Pre, Post} ->
Doc = document_reduce(fun combine_comma_newline/2, ExprsD),
{document_fail(), combine_pre_comments(Pre, combine_post_comments(Post, Doc))}
end.
%% Because the spec syntax is different from regular function syntax,
%% in the general case we have to indent them differently, but with just
%% one clause we can indent them like functions, which seems more natural.
single_clause_spec_to_algebra(Name, {spec_clause, CMeta, Head, Body, Guards}) ->
{args, AMeta, Args} = Head,
clauses_to_algebra([{spec_clause, CMeta, {call, AMeta, Name, Args}, Body, Guards}]).
receive_after_to_algebra(Expr, Body) ->
{Pre, []} = comments(element(2, Expr)),
ExprD = do_expr_to_algebra(Expr),
BodyD = block_to_algebra(Body),
HeadD = wrap(document_text("after "), ExprD, document_text(" ->")),
CommentHeadD = combine_pre_comments(Pre, HeadD),
document_choice(
combine_space(CommentHeadD, document_single_line(BodyD)),
combine_nested(CommentHeadD, BodyD)
).
try_to_algebra(Exprs, OfClauses, CatchClauses, After) ->
Clauses =
[try_of_block(Exprs, OfClauses)] ++
[try_catch_to_algebra(CatchClauses) || CatchClauses =/= []] ++
[try_after_to_algebra(After) || After =/= []] ++
[document_text("end")],
document_reduce(fun combine_newline/2, Clauses).
try_catch_to_algebra(Clauses) ->
combine_nested(document_text("catch"), clauses_to_algebra(Clauses)).
try_after_to_algebra(Exprs) ->
AfterD = document_text("after"),
ExprsD = block_to_algebra(Exprs),
document_choice(
combine_space(AfterD, document_single_line(ExprsD)),
combine_nested(AfterD, ExprsD)
).
try_of_block(Exprs, OfClauses) ->
TryD = document_text("try"),
OfD = document_text("of"),
ExprsD = block_to_algebra(Exprs),
TrySingle = combine_space(TryD, document_single_line(ExprsD)),
TryMulti = combine_nested(TryD, ExprsD),
case OfClauses of
[] ->
document_choice(TrySingle, TryMulti);
_ ->
combine_nested(
document_choice(
combine_space(TrySingle, OfD),
combine_newline(TryMulti, OfD)
),
clauses_to_algebra(OfClauses)
)
end.
break_between(Left, Right) ->
erlfmt_scan:get_end_line(Left) < erlfmt_scan:get_line(Right).
next_break_fits_op(Op, Expr) ->
lists:member(Op, ?NEXT_BREAK_FITS_OPS) andalso
next_break_fits(Expr, [call, macro_call]).
next_break_fits(Expr, Extra) ->
lists:member(element(1, Expr), Extra ++ ?NEXT_BREAK_FITS) andalso
no_comments_or_parens(Expr).
next_break_fits(Expr) -> next_break_fits(Expr, []).
no_comments_or_parens(Meta) ->
{Pre, Post} = comments(Meta),
Pre =:= [] andalso Post =:= [] andalso not erlfmt_scan:get_anno(parens, Meta, false).
maybe_wrap_in_parens(Meta, Doc) ->
Parens = erlfmt_scan:get_anno(parens, Meta, false),
if
Parens -> wrap_in_parens(Doc);
true -> Doc
end.
combine_comments(Meta, Doc) ->
{Pre, Post} = comments(Meta),
combine_post_comments(Post, combine_pre_comments(Pre, Doc)).
combine_pre_comments([], Doc) -> Doc;
combine_pre_comments(Comments, Doc) -> combine_newline(comments_to_algebra(Comments), Doc).
combine_post_comments([], Doc) -> Doc;
combine_post_comments(Comments, Doc) -> combine_newline(Doc, comments_to_algebra(Comments)).
comments_to_algebra(Comments) ->
%% TODO: should we add spaces in between?
CommentsD = lists:map(fun comment_to_algebra/1, Comments),
document_reduce(fun combine_newline/2, CommentsD).
comment_to_algebra({comment, _Meta, Lines}) ->
LinesD = lists:map(fun erlfmt_algebra:document_text/1, Lines),
document_reduce(fun combine_newline/2, LinesD).
comments(Meta) ->
{erlfmt_scan:get_anno(pre_comments, Meta, []),
erlfmt_scan:get_anno(post_comments, Meta, [])}.