Current section
Files
Jump to
Current section
Files
src/fn_pp.erl
%% Copyright 2015 Mariano Guerra
%%
%% 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(fn_pp).
-export([format/1, layout/1]).
-import(prettypr, [sep/1, beside/2, empty/0, text/1, floating/1, nest/2, par/2,
above/2, follow/3]).
-import(erl_parse, [inop_prec/1, preop_prec/1]).
% used on tests
-export([pp_guards/2, default_ctx/0]).
-include("efene.hrl").
-define(PADDING, 2).
-define(PAPER, 80). % 80
-define(RIBBON, 56). % 56
-define(NOUSER, undefined).
-define(NOHOOK, none).
-record(ctxt, {prec = 0 :: integer(),
sub_indent = 2 :: non_neg_integer(),
exports_all = false,
exports = #{},
break_indent = 4 :: non_neg_integer(),
paper = ?PAPER :: integer(),
ribbon = ?RIBBON :: integer()}).
layout(V) -> layout(V, default_ctx()).
layout(V, Ctx) when is_list(V) -> pp_mod(V, Ctx);
layout(V, Ctx) -> pp(V, Ctx).
format(V) -> prettypr:format(layout(V), ?PAPER, ?RIBBON).
default_ctx() -> #ctxt{}.
pp_mod([], _Ctx) -> empty();
pp_mod([Node | Nodes], Ctx) ->
Ctx1 = maybe_update_ctx(Node, Ctx),
above(pp(Node, Ctx1), pp_mod(Nodes, Ctx1)).
maybe_update_ctx({attribute, _, compile, export_all}, Ctx) ->
Ctx1 = Ctx#ctxt{exports_all=true},
Ctx1;
maybe_update_ctx({attribute, _, export, Exports}, Ctx=#ctxt{exports=CurExports}) ->
NewExports = maps:merge(CurExports, maps:from_list([{K, true} || K <- Exports])),
Ctx1 = Ctx#ctxt{exports=NewExports},
Ctx1;
maybe_update_ctx(_Node, Ctx) ->
Ctx.
function_exported(#ctxt{exports_all=true}, _, _) -> true;
function_exported(#ctxt{exports=Exports}, Name, Arity) ->
maps:is_key({Name, Arity}, Exports).
arity_to_list('_') -> "`_`";
arity_to_list(V) -> integer_to_list(V).
pp_fn_ref({FNameAtom, Arity}, _Ctx) ->
text(atom_to_list(FNameAtom) ++ "/" ++ arity_to_list(Arity)).
pp_fn_deprecated_ref({FName, Arity, When}, _Ctx) ->
text("(" ++ atom_to_list(FName) ++ ", " ++ arity_to_list(Arity) ++ ", "
++ atom_to_list(When) ++ ")");
pp_fn_deprecated_ref(FnRef, Ctx) ->
pp_fn_ref(FnRef, Ctx).
pp_attr_fun_list(Prefix, Funs, Ctx) ->
besidel([text(Prefix),
join(Funs, Ctx, fun pp_fn_ref/2, comma_f()),
cparen_f()]).
gen_attr(Attr, V) when is_atom(V) ->
text("@" ++ atom_to_list(Attr) ++ "(" ++ quote_atom_raw(V) ++ ")");
gen_attr(Attr, V) when is_list(V) ->
text("@" ++ atom_to_list(Attr) ++ "(" ++ io_lib:write_string(V) ++ ")").
% TODO: what to do with errors
pp({error, _}, _Ctx) -> empty();
% TODO: handle specs
pp({attribute, _, spec, _}, _Ctx) -> empty();
% TODO: handle opaque
pp({attribute, _, opaque, _}, _Ctx) -> empty();
% TODO: handle dialyzer
pp({attribute, _, dialyzer, _}, _Ctx) -> empty();
% TODO: handle callback
pp({attribute, _, callback, _}, _Ctx) -> empty();
% TODO: handle optional_callbacks
pp({attribute, _, optional_callbacks, _}, _Ctx) -> empty();
pp({attribute, _, file, _}, _Ctx) -> empty();
pp({attribute, _, module, _}, _Ctx) -> empty();
pp({attribute, _, Attr=behaviour, V}, _Ctx) ->
gen_attr(Attr, V);
pp({attribute, _, behavior, V}, _Ctx) ->
gen_attr(behaviour, V);
pp({attribute, _, Attr=author, V}, _Ctx) ->
gen_attr(Attr, V);
pp({attribute, _, Attr=vsn, V}, _Ctx) ->
gen_attr(Attr, V);
pp({attribute, _, Attr=date, V}, _Ctx) ->
gen_attr(Attr, V);
pp({attribute, _, Attr=vc, V}, _Ctx) ->
gen_attr(Attr, V);
pp({attribute, _, import, {ModNameAtom, Imports}}, Ctx) ->
beside(text("@import("),
followc(Ctx, beside(text(atom_to_list(ModNameAtom)), comma_f()),
beside(wrap_list(join(Imports, Ctx, fun pp_fn_ref/2, comma_f())), cparen_f())));
pp({attribute, _, export_type, Exports}, Ctx) ->
pp_attr_fun_list("@export_type(", Exports, Ctx);
pp({attribute, _, on_load, V={_FName, _Arity}}, Ctx) ->
besidel([text("@on_load("), pp_fn_ref(V, Ctx), cparen_f()]);
pp({attribute, _, deprecated, V={_FName, _Arity, _When}}, Ctx) ->
besidel([text("@deprecated("), pp_fn_deprecated_ref(V, Ctx), cparen_f()]);
pp({attribute, _, deprecated, module}, _Ctx) ->
text("@deprecated(module)");
pp({attribute, _, deprecated, Funs}, Ctx) ->
besidel([text("@deprecated("),
join(Funs, Ctx, fun pp_fn_deprecated_ref/2, comma_f()),
cparen_f()]);
pp({attribute, _, inline, Exports}, Ctx) ->
pp_attr_fun_list("@inline(", Exports, Ctx);
pp({attribute, _, type, {PName, Type, _Vars}}, Ctx) ->
followc(Ctx, text("@type(" ++ atom_to_list(PName) ++ ") ->"),
pp_type(Type, Ctx));
pp({attribute, _, export, _}, _Ctx) -> empty();
pp({attribute, _, compile, export_all}, _Ctx) -> text("@compile(export_all)");
pp({attribute, _, compile, _}, _Ctx) -> empty();
pp({attribute, _, record, {Name, Fields}}, Ctx) ->
followc(Ctx, wrap(text(atom_to_list(Name)), text("@record("), text(") ->")),
wrap_paren(join(Fields, Ctx, fun pp_rec_def/2, comma_f())));
pp({var, _, V}, _Ctx) -> text(atom_to_list(V));
pp({integer, _, Num}, _Ctx) -> text(integer_to_list(Num));
pp({float, _, Num}, _Ctx) -> text(io_lib:write(Num));
pp({char, _, V}, _Ctx) -> text("#c " ++ io_lib:write_string([V]));
pp({atom, _, V}, _Ctx) -> quote_atom(V);
pp({string, _, V}, _Ctx) -> text(io_lib:write_string(V));
pp({bin, _, [{bin_element, _, {string, _, V}, default, default}]}, _Ctx) ->
text(io_lib:write_string(V, $'));
%pp({bin, _, []}, _Ctx) -> text("#b {}");
pp({bin, _, Elems}, Ctx) -> beside(text("#b "), wrap_map(pp_bin_es(Elems, Ctx)));
pp({tuple, _, []}, _Ctx) -> text("()");
pp({tuple, _, [Item]}, Ctx) ->
wrap_paren(beside(pp(Item, Ctx), comma_f()));
pp({tuple, _, Items}, Ctx) ->
wrap_paren(pp_items(Items, Ctx));
pp({map, _, []}, _Ctx) -> text("{}");
pp({map, _, Items}, Ctx) ->
pp_map(Items, Ctx);
pp({map, _, CurMap, Items}, Ctx) ->
wrap(text(" # "), pp(CurMap, Ctx), pp_map(Items, Ctx));
pp({record, _, RName, Args}, Ctx) ->
beside(text("#r." ++ atom_to_list(RName) ++ " "),
wrap_map(join(Args, Ctx, fun pp_rec_pair/2, comma_f())));
pp({record, _, CurRec, RName, Args}, Ctx) ->
beside(text("#r." ++ atom_to_list(RName) ++ " "),
wrap(text(" # "), pp(CurRec, Ctx), wrap_map(join(Args, Ctx, fun pp_rec_pair/2, comma_f()))));
pp({record_field, _, RVal, RName, Field}, Ctx) ->
sep([beside(text("#r." ++ atom_to_list(RName) ++ "."), pp(Field, Ctx)),
pp(RVal, Ctx)]);
pp({record_index, _, RName, Field}, Ctx) ->
beside(text("#r." ++ atom_to_list(RName) ++ " "), pp(Field, Ctx));
pp({'catch', _, Expr}, Ctx) ->
beside(text("catch "), pp(Expr, Ctx));
% fun references
pp({'fun', Line, {function, FName, Arity}}, Ctx) ->
beside(text("fn "), wrap(colon_f(), pp({atom, Line, FName}, Ctx), pp({integer, Line, Arity}, Ctx)));
pp({'fun', _, {function, MName, FName, Arity}}, Ctx) ->
beside(text("fn "), wrap(colon_f(), wrap(dot_f(), pp(MName, Ctx), pp(FName, Ctx)), pp(Arity, Ctx)));
pp({nil, _}, _Ctx) -> text("[]");
pp(V={cons, _, _H, _T}, Ctx) -> pp_cons(V, Ctx);
pp({call, _, {remote, _, MName, FName}, Args}, Ctx) ->
pp_call(MName, FName, Args, Ctx);
pp({call, _, FName, Args}, Ctx) ->
pp_call(FName, Args, Ctx);
pp({match, Line, Left, Right}, Ctx) ->
% reuse logic
pp({op, Line, '=', Left, Right}, Ctx);
pp({op, _, Op, Left, Right}, Ctx) ->
{LeftPrec, Prec, RightPrec} = inop_prec(Op),
D1 = pp(Left, Ctx#ctxt{prec=LeftPrec}),
D2 = text(atom_to_list(fn_to_erl:map_op_reverse(Op))),
D3 = pp(Right, Ctx#ctxt{prec=RightPrec}),
D4 = parc(Ctx, [D1, D2, D3]),
maybe_paren(Prec, Ctx#ctxt.prec, D4);
% unary
pp({op, _, Op, Right}, Ctx) ->
{Prec, RightPrec} = preop_prec(Op),
LOp = text(atom_to_list(fn_to_erl:map_op_reverse(Op))),
LRight = pp(Right, Ctx#ctxt{prec=RightPrec}),
L = parc(Ctx, [LOp, LRight]),
maybe_paren(Prec, Ctx#ctxt.prec, L);
pp({lc, _, {block, _, Body}, Gens}, Ctx) ->
Ctx1 = reset_prec(Ctx),
pp_for(Gens, Ctx1, pp_body(Body, Ctx1), "for");
pp({lc, _, Body, Gens}, Ctx) ->
Ctx1 = reset_prec(Ctx),
pp_for(Gens, Ctx1, pp(Body, Ctx1), "for");
pp({bc, _, {block, _, Body}, Gens}, Ctx) ->
Ctx1 = reset_prec(Ctx),
pp_for(Gens, Ctx1, pp_body(Body, Ctx1), "#b for");
pp({bc, _, Body, Gens}, Ctx) ->
Ctx1 = reset_prec(Ctx),
pp_for(Gens, Ctx1, pp(Body, Ctx1), "#b for");
pp({block, _, [Expr]}, Ctx) ->
Ctx1 = reset_prec(Ctx),
sep([
text("begin"),
nestc(Ctx1, pp(Expr, Ctx1)),
text("end")
]);
pp({block, _, Body}, Ctx) ->
Ctx1 = reset_prec(Ctx),
above(text("begin"),
above(nestc(Ctx1, pp_body(Body, Ctx1)),
text("end")));
pp({'try', _, Body, [], Clauses, AfterBody}, Ctx0) ->
Ctx = reset_prec(Ctx0),
above(text("try"),
above(
maybe_above(
maybe_above(
nestc(Ctx, pp_body(Body, Ctx)),
pp_try_catch_clauses(Clauses, Ctx)),
pp_try_after(AfterBody, Ctx)),
text("end")));
pp({'try', _, Expr, OfCases, Clauses, AfterBody}, Ctx0) ->
Ctx = reset_prec(Ctx0),
abovel([besidel([text("try "), pp_body(Expr, Ctx)]),
maybe_above(
maybe_above(nestc(Ctx, pp_case_clauses(OfCases, Ctx)),
pp_try_catch_clauses(Clauses, Ctx)),
pp_try_after(AfterBody, Ctx)),
text("end")]);
% receive no after
pp({'receive', _, Clauses}, Ctx0) ->
Ctx = reset_prec(Ctx0),
abovel([text("receive"),
pp_case_clauses(Clauses, Ctx),
text("end")]);
pp({'receive', _, [], AfterExpr, AfterBody}, Ctx0) ->
Ctx = reset_prec(Ctx0),
abovel([besidel([text("receive"), text(" after "), pp(AfterExpr, Ctx), colon_f()]),
nestc(Ctx, pp_body(AfterBody, Ctx)),
text("end")]);
pp({'receive', _, Clauses, AfterExpr, AfterBody}, Ctx0) ->
Ctx = reset_prec(Ctx0),
abovel([text("receive"),
pp_case_clauses(Clauses, Ctx),
besidel([text("after "), pp(AfterExpr, Ctx), colon_f()]),
nestc(Ctx, pp_body(AfterBody, Ctx)),
text("end")]);
pp({'if', _, Clauses}, Ctx) ->
pp_if_clauses(Clauses, Ctx, first);
pp({'case', _, Expr, Clauses}, Ctx) ->
above(parc(Ctx, [text("match"), beside(pp(Expr, Ctx), colon_f())]),
above(pp_case_clauses(Clauses, Ctx),
text("end")));
% special case one clause, put it in the same line
pp({'fun', _, {clauses, [Clause]}}, Ctx) ->
above(pp_case_clause(Clause, Ctx, "fn case"),
text("end"));
pp({'fun', _, {clauses, Clauses}}, Ctx) ->
above(sep([text("fn"), pp_case_clauses(Clauses, Ctx)]),
text("end"));
pp({named_fun, _, AName, Clauses}, Ctx) ->
above(sep([text("fn " ++ atom_to_list(AName)), pp_case_clauses(Clauses, Ctx)]),
text("end"));
pp({function, _, Name, Arity, Clauses}, Ctx) ->
IsExported = function_exported(Ctx, Name, Arity),
Attrs = if IsExported -> text("@public"); true -> empty() end,
% HACK: force a new line above each top level function
above(sep([text("\nfn"), quote_atom(Name), Attrs]),
above(pp_case_clauses(Clauses, Ctx),
text("end")));
pp({eof, _}, _Ctx) -> empty().
pp_bin_es(Es, Ctx) -> join(Es, Ctx, fun pp_bin_e/2, comma_f()).
pp_bin_e({bin_element, _, Left, default, [binary]}, Ctx) ->
wrap_pair(Ctx, colon_f(), pp(Left, Ctx), text("binary"));
pp_bin_e({bin_element, _, Left, Size, default}, Ctx) when Size =/= default ->
wrap_pair(Ctx, colon_f(), pp(Left, Ctx), pp(Size, Ctx));
pp_bin_e({bin_element, _, Left, default, default}, Ctx) ->
wrap_pair(Ctx, colon_f(), pp(Left, Ctx), text("_"));
pp_bin_e({bin_element, _, Left, Size, Types}, Ctx) ->
TypeMap = pp_bin_e_types(Types, Size, Ctx),
wrap_pair(Ctx, colon_f(), pp(Left, Ctx), TypeMap).
pp_bin_e_types(Types, Size, Ctx) ->
pp_bin_e_types([{size, Size} | Types], Ctx).
pp_bin_e_types(Types, Ctx) ->
wrap_map(join(Types, Ctx, fun pp_bin_e_type/2, comma_f())).
pp_attr_pair(Ctx, KeyTxt, ValTxt) ->
wrap_pair(Ctx, colon_f(), text(KeyTxt), text(ValTxt)).
pp_bin_e_type({size, default}, _Ctx) -> empty();
pp_bin_e_type({size, V}, Ctx) ->
wrap_pair(Ctx, colon_f(), text("size"), pp(V, Ctx));
pp_bin_e_type({unit, V}, Ctx) ->
pp_attr_pair(Ctx, "unit", integer_to_list(V));
pp_bin_e_type(Type=integer, Ctx) ->
pp_attr_pair(Ctx, "type", atom_to_list(Type));
pp_bin_e_type(Type=float, Ctx) ->
pp_attr_pair(Ctx, "type", atom_to_list(Type));
pp_bin_e_type(Type=binary, Ctx) ->
pp_attr_pair(Ctx, "type", atom_to_list(Type));
pp_bin_e_type(Type=bytes, Ctx) ->
pp_attr_pair(Ctx, "type", atom_to_list(Type));
pp_bin_e_type(Type=bitstring, Ctx) ->
pp_attr_pair(Ctx, "type", atom_to_list(Type));
pp_bin_e_type(Type=bits, Ctx) ->
pp_attr_pair(Ctx, "type", atom_to_list(Type));
pp_bin_e_type(Type=utf8, Ctx) ->
pp_attr_pair(Ctx, "type", atom_to_list(Type));
pp_bin_e_type(Type=utf16, Ctx) ->
pp_attr_pair(Ctx, "type", atom_to_list(Type));
pp_bin_e_type(Type=utf32, Ctx) ->
pp_attr_pair(Ctx, "type", atom_to_list(Type));
pp_bin_e_type(Type=signed, Ctx) ->
pp_attr_pair(Ctx, "sign", atom_to_list(Type));
pp_bin_e_type(Type=unsigned, Ctx) ->
pp_attr_pair(Ctx, "sign", atom_to_list(Type));
pp_bin_e_type(Type=big, Ctx) ->
pp_attr_pair(Ctx, "endianness", atom_to_list(Type));
pp_bin_e_type(Type=little, Ctx) ->
pp_attr_pair(Ctx, "endianness", atom_to_list(Type));
pp_bin_e_type(Type=native, Ctx) ->
pp_attr_pair(Ctx, "endianness", atom_to_list(Type)).
pp_call(FName, Args, Ctx) ->
pp_call_f(FName, Args, Ctx, fun pp/2).
pp_call(MName, FName, Args, Ctx) ->
pp_call_f(MName, FName, Args, Ctx, fun pp/2).
pp_call_f(FName, Args, Ctx, PPFun) ->
beside(pp_call_pos(FName, Ctx), pp_args(Args, Ctx, PPFun)).
pp_call_f(MName, FName, Args, Ctx, PPFun) ->
beside(wrap(dot_f(), pp_call_pos(MName, Ctx), pp_call_pos(FName, Ctx)), pp_args(Args, Ctx, PPFun)).
pp_map(Items, Ctx) ->
wrap_map(join(Items, Ctx, fun pp_pair/2, comma_f())).
pp_type_map(Items, Ctx) ->
wrap_map(join(Items, Ctx, fun pp_pair_type/2, comma_f())).
pp_lc_gens(Items, Ctx) ->
join(Items, Ctx, fun pp_lc_gen/2, scolon_f()).
pp_lc_gen({generate, _, Left, Right}, Ctx) ->
wrap(text(" in "), pp(Left, Ctx), pp(Right, Ctx));
% if there's a b_generate the for loop should be tagged with #b so it should be
% handled as a b_generate anyway?
pp_lc_gen({b_generate, _, Left, Right}, Ctx) ->
wrap(text(" in "), pp(Left, Ctx), pp(Right, Ctx));
pp_lc_gen(Filter, Ctx) ->
beside(text("when "), pp(Filter, Ctx)).
pp_for(Gens, Ctx, BodyL, Kw) ->
above(sep([text(Kw), beside(pp_lc_gens(Gens, Ctx), colon_f())]),
above(nestc(Ctx, BodyL),
text("end"))).
wrap_list(Items) ->
wrap(Items, olist_f(), clist_f()).
wrap_map(Items) ->
wrap(Items, omap_f(), cmap_f()).
wrap_paren(Items) ->
wrap(Items, oparen_f(), cparen_f()).
wrap(Items, Open, Close) ->
beside(Open, beside(Items, Close)).
abovel([]) -> empty();
abovel([H]) -> H;
% maybe skip empty() here?
abovel([H | T]) -> above(H, abovel(T)).
besidel([]) -> empty();
besidel([H]) -> H;
besidel([H | T]) -> beside(H, besidel(T)).
% null is empty()
maybe_above(L, null) -> L;
maybe_above(null, L) -> L;
maybe_above(LLeft, LRight) -> above(LLeft, LRight).
nestc(Ctx, Layout) ->
nest(Ctx#ctxt.sub_indent, Layout).
followc(Ctx, L1, L2) ->
follow(L1, L2, Ctx#ctxt.sub_indent * 2).
parc(Ctx, L) ->
par(L, Ctx#ctxt.sub_indent).
pp_try_catch_clauses([], _Ctx) -> empty();
pp_try_catch_clauses(Clauses, Ctx) ->
above(text("catch"),
nestc(Ctx, pp_try_catch_cases(Clauses, Ctx))).
pp_try_after([], _Ctx) -> empty();
pp_try_after(Body, Ctx) ->
above(text("after"), nestc(Ctx, pp_body(Body, Ctx))).
pp_try_catch_cases([], _Ctx) -> empty();
pp_try_catch_cases([H | T], Ctx) ->
above(pp_try_catch_case(H, Ctx), pp_try_catch_cases(T, Ctx)).
pp_try_catch_case({clause, _, [{tuple, _, TItems}], [], Body}, Ctx) ->
pp_header_and_body(Ctx,
beside(text("case "), beside(pp_try_catch_case_items(TItems, Ctx), colon_f())),
Body);
pp_try_catch_case({clause, _, [{tuple, _, TItems}], Guards, Body}, Ctx) ->
pp_header_and_body(Ctx,
followc(Ctx,
beside(text("case "), pp_try_catch_case_items(TItems, Ctx)),
beside(text("when "), beside(pp_guards(Guards, Ctx), colon_f()))),
Body).
pp_try_catch_case_items([{atom, _, throw}, Var, {var, _, '_'}], Ctx) ->
pp(Var, Ctx);
pp_try_catch_case_items([Type, Var, {var, _, '_'}], Ctx) ->
pp_items([Type, Var], Ctx);
pp_try_catch_case_items([Type, Var, StackTrace], Ctx) ->
pp_items([Type, Var, StackTrace], Ctx).
pp_call_pos(V={var, _, _}, Ctx) -> pp(V, Ctx);
pp_call_pos(V={atom, _, _}, Ctx) -> pp(V, Ctx);
pp_call_pos(V, Ctx) -> beside(oparen_f(), beside(pp(V, Ctx), cparen_f())).
pp_args([], _Ctx, _PPFun) -> text("()");
pp_args(Args, Ctx, PPFun) ->
beside(oparen_f(), beside(pp_args_inn(Args, Ctx, PPFun), cparen_f())).
pp_args_inn(Args, Ctx) -> pp_args_inn(Args, Ctx, fun pp/2).
pp_args_inn([], _Ctx, _PPFun) -> empty();
pp_args_inn([Arg], Ctx, PPFun) -> PPFun(Arg, Ctx);
pp_args_inn(Args, Ctx, PPFun) -> join(Args, Ctx, PPFun, comma_f()).
pp_case_clauses([Clause], Ctx) ->
pp_case_clause(Clause, Ctx);
pp_case_clauses([Clause | Clauses], Ctx) ->
above(pp_case_clause(Clause, Ctx), pp_case_clauses(Clauses, Ctx)).
pp_case_clause(Clause, Ctx) ->
pp_case_clause(Clause, Ctx, "case").
pp_case_clause({clause, _, [], [], Body}, Ctx, Kw) ->
pp_header_and_body(Ctx, text(Kw ++ ":"), Body);
pp_case_clause({clause, _, Patterns, [], Body}, Ctx, Kw) ->
pp_header_and_body(Ctx,
beside(text(Kw ++ " "), beside(pp_args_inn(Patterns, Ctx), colon_f())),
Body);
pp_case_clause({clause, _, Patterns, Guards, Body}, Ctx, Kw) ->
pp_header_and_body(Ctx,
followc(Ctx,
beside(text(Kw ++ " "), pp_args_inn(Patterns, Ctx)),
beside(text("when "), beside(pp_guards(Guards, Ctx), colon_f()))),
Body).
pp_if_clauses([Clause], Ctx, GuardPos) ->
NewGuardPos = if GuardPos == first -> first; true -> last end,
above(pp_if_clause(Clause, Ctx, NewGuardPos),
text("end"));
pp_if_clauses([Clause | Clauses=[_|_]], Ctx, GuardPos) ->
above(pp_if_clause(Clause, Ctx, GuardPos),
pp_if_clauses(Clauses, Ctx, middle)).
pp_if_clause({clause, _, _, [[{atom, _, true}]], Body}, Ctx, last) ->
pp_header_and_body(Ctx, text("else:"), Body);
pp_if_clause({clause, _, _, Guards, Body}, Ctx, first) ->
pp_header_and_body(Ctx,
pp_if_header(Ctx, "when ", Guards),
Body);
pp_if_clause({clause, _, _, Guards, Body}, Ctx, _) ->
pp_header_and_body(Ctx,
pp_if_header(Ctx, "else ", Guards),
Body).
pp_if_header(Ctx, KwT, Guards) ->
beside(text(KwT), beside(pp_guards(Guards, Ctx), colon_f())).
pp_header_and_body(Ctx, HeaderLayout, Body) ->
sep([HeaderLayout, nestc(Ctx, pp_body(Body, Ctx))]).
pp_guards(Guards, Ctx) ->
join(Guards, Ctx, fun pp_guard/2, scolon_f()).
pp_guard(SGuards, Ctx) ->
join(SGuards, Ctx, fun pp/2, comma_f()).
join(Items, Ctx, PPFun, Sep) ->
join(Items, Ctx, PPFun, Sep, []).
join([], _Ctx, _PPFun, _Sep, []) ->
empty();
join([Item], Ctx, PPFun, _Sep, []) ->
PPFun(Item, Ctx);
join([Item], Ctx, PPFun, _Sep, Accum) ->
par(lists:reverse([PPFun(Item, Ctx) | Accum]), 2);
join([H | T=[_|_]], Ctx, PPFun, Sep, Accum) ->
join(T, Ctx, PPFun, Sep, [beside(PPFun(H, Ctx), Sep) | Accum]);
join([H | T], Ctx, PPFun, Sep, Accum) ->
join([T], Ctx, PPFun, Sep, [beside(PPFun(H, Ctx), Sep) | Accum]).
pp_items(Items, Ctx) ->
join(Items, Ctx, fun pp/2, comma_f()).
pp_pair({map_field_assoc, _, K, V}, Ctx) ->
wrap_pair(Ctx, colon_f(), pp(K, Ctx), pp(V, Ctx));
pp_pair({map_field_exact, _, K, V}, Ctx) ->
wrap_pair(Ctx, equal_f(), pp(K, Ctx), pp(V, Ctx)).
pp_pair_type({type, _, map_field_assoc, [K, V]}, Ctx) ->
wrap_pair(Ctx, colon_f(), pp_type(K, Ctx), pp_type(V, Ctx));
pp_pair_type({type, _, map_field_exact, [K, V]}, Ctx) ->
wrap_pair(Ctx, equal_f(), pp_type(K, Ctx), pp_type(V, Ctx)).
wrap_pair(Ctx, Sep, Left, Right) ->
parc(Ctx, [beside(Left, Sep), Right]).
pp_rec_pair({record_field, _, K, V}, Ctx) ->
parc(Ctx, [beside(pp(K, Ctx), colon_f()), pp(V, Ctx)]).
pp_rec_def({record_field, _, Name}, Ctx) -> pp(Name, Ctx);
pp_rec_def({record_field, _, Name, Val}, Ctx) -> wrap(equal_f(), pp(Name, Ctx), pp(Val, Ctx));
pp_rec_def({typed_record_field, {record_field, _, Name}, Type}, Ctx) ->
followc(Ctx, beside(pp(Name, Ctx), text(" is")), pp_type(Type, Ctx));
pp_rec_def({typed_record_field, {record_field, _, Name, Val}, Type}, Ctx) ->
followc(Ctx, beside(wrap(equal_f(), pp(Name, Ctx), pp(Val, Ctx)), text(" is")), pp_type(Type, Ctx)).
pp_type({ann_type, _, [Type, AnnType]}, Ctx) ->
wrap(text(" is "), pp_type(Type, Ctx), pp_type(AnnType, Ctx));
pp_type(V={op, _, _, _}, Ctx) -> pp(V, Ctx);
pp_type(V={op, _, _, _, _}, Ctx) -> pp(V, Ctx);
pp_type(V={atom, _, _}, Ctx) -> pp(V, Ctx);
pp_type(V={var, _, _}, Ctx) -> pp(V, Ctx);
pp_type(V={integer, _, _}, Ctx) -> pp(V, Ctx);
pp_type({user_type, Line, Name, Args}, Ctx) ->
pp_call_f({atom, Line, Name}, Args, Ctx, fun pp_type/2);
pp_type({type, _, tuple, any}, _Ctx) ->
text("tuple()");
pp_type({type, _, map, any}, _Ctx) ->
text("map()");
pp_type({type, _, list, []}, _Ctx) ->
text("list()");
pp_type({type, _, tuple, [Item]}, Ctx) ->
wrap_paren(beside(pp_type(Item, Ctx), comma_f()));
pp_type({type, _, tuple, Items}, Ctx) ->
wrap_paren(join(Items, Ctx, fun pp_type/2, comma_f()));
pp_type({type, _, list, Args}, Ctx) ->
wrap_list(join(Args, Ctx, fun pp_type/2, comma_f()));
pp_type({type, _, map, Args}, Ctx) ->
pp_type_map(Args, Ctx);
%wrap_map(join(Args, Ctx, fun pp_type_map/2, comma_f()));
pp_type({type, _, union, Args}, Ctx) ->
wrap_list(join(Args, Ctx, fun pp_type/2, text(" or")));
pp_type({remote_type, _, [AMod, AFName, Args]}, Ctx) ->
pp_call_f(AMod, AFName, Args, Ctx, fun pp_type/2);
pp_type({type, Line, Name, Args}, Ctx) ->
pp_call_f({atom, Line, Name}, Args, Ctx, fun pp_type/2).
pp_cons(V, Ctx) ->
case cons_to_list(V, []) of
[A | B] when not is_list(A), not is_list(B) ->
followc(Ctx, beside(pp(A, Ctx), text(" ::")), pp(B, Ctx));
[A | B] when not is_list(B) ->
followc(Ctx, beside(wrap_list(pp_items(A, Ctx)), text(" ::")), pp(B, Ctx));
L -> wrap_list(pp_items(L, Ctx))
end.
cons_to_list({cons, _, H, {nil, _}}, Accum) ->
% proper list tail
lists:reverse([H | Accum]);
cons_to_list({cons, _, H, T={cons, _, _, _}}, Accum) ->
cons_to_list(T, [H | Accum]);
cons_to_list({cons, _, H, T}, []) ->
[H | T];
cons_to_list({cons, _, H, T}, Accum) ->
% improper list
[lists:reverse([H | Accum]) | T].
% not sure if the best way
pp_body([], _Ctx) ->
empty();
pp_body([H | T], Ctx) ->
above(pp(H, Ctx), pp_body(T, Ctx)).
olist_f() -> floating(text("[")).
clist_f() -> floating(text("]")).
oparen_f() -> floating(text("(")).
cparen_f() -> floating(text(")")).
omap_f() -> floating(text("{")).
cmap_f() -> floating(text("}")).
equal_f() -> floating(text("=")).
comma_f() -> floating(text(",")).
dot_f() -> floating(text(".")).
colon_f() -> floating(text(":")).
scolon_f() -> floating(text(";")).
maybe_paren(P, Prec, Expr) when P < Prec ->
beside(beside(oparen_f(), Expr), cparen_f());
maybe_paren(_P, _Prec, Expr) ->
Expr.
set_prec(Ctxt, Prec) ->
Ctxt#ctxt{prec = Prec}. % used internally
reset_prec(Ctxt) ->
set_prec(Ctxt, 0). % used internally
quote_atom(V) ->
text(quote_atom_raw(V)).
quote_atom_raw(V) ->
Chars = atom_to_list(V),
% will quote all erlang reserved words, I think it's ok
case io_lib:quote_atom(V, Chars) of
true -> io_lib:write_string(Chars, $`);
false ->
case fn_lexer:is_reserved(Chars) of
true -> io_lib:write_string(Chars, $`);
false -> Chars
end
end.