1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 1999-2018. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20%% Purpose : Core Erlang (naive) prettyprinter 21 22-module(core_pp). 23 24-export([format/1,format_all/1]). 25 26-include("core_parse.hrl"). 27 28%% ====================================================================== %% 29%% format(Node) -> Text 30%% Node = coreErlang() 31%% Text = string() | [Text] 32%% 33%% Prettyprint-formats (naively) an abstract Core Erlang syntax 34%% tree. 35 36-record(ctxt, {indent = 0 :: integer(), 37 item_indent = 2 :: integer(), 38 body_indent = 4 :: integer(), 39 line = 0 :: integer(), 40 clean = true :: boolean()}). 41 42-define(TAB_WIDTH, 8). 43 44-spec format(cerl:cerl()) -> iolist(). 45 46format(Node) -> 47 format(Node, #ctxt{}). 48 49-spec format_all(cerl:cerl()) -> iolist(). 50 51format_all(Node) -> 52 format(Node, #ctxt{clean=false}). 53 54maybe_anno(Node, Fun, #ctxt{clean=false}=Ctxt) -> 55 As = cerl:get_ann(Node), 56 maybe_anno(Node, Fun, Ctxt, As); 57maybe_anno(Node, Fun, #ctxt{clean=true}=Ctxt) -> 58 As0 = cerl:get_ann(Node), 59 case get_line(As0) of 60 none -> 61 maybe_anno(Node, Fun, Ctxt, As0); 62 Line -> 63 As = strip_line(As0), 64 if Line > Ctxt#ctxt.line -> 65 [io_lib:format("%% Line ~w",[Line]), 66 nl_indent(Ctxt), 67 maybe_anno(Node, Fun, Ctxt#ctxt{line = Line}, As) 68 ]; 69 true -> 70 maybe_anno(Node, Fun, Ctxt, As) 71 end 72 end. 73 74maybe_anno(Node, Fun, Ctxt, []) -> 75 Fun(Node, Ctxt); 76maybe_anno(Node, Fun, Ctxt, List) -> 77 Ctxt1 = add_indent(Ctxt, 2), 78 Ctxt2 = add_indent(Ctxt1, 3), 79 ["( ", 80 Fun(Node, Ctxt1), 81 nl_indent(Ctxt1), 82 "-| ",format_anno(List, Ctxt2)," )" 83 ]. 84 85format_anno([_|_]=List, Ctxt) -> 86 [$[,format_anno_list(List, Ctxt),$]]; 87format_anno({file,Name}, _Ctxt) -> 88 %% Optimization: Reduces file size considerably. 89 io_lib:format("{'file',~p}", [Name]); 90format_anno(Tuple, Ctxt) when is_tuple(Tuple) -> 91 [${,format_anno_list(tuple_to_list(Tuple), Ctxt),$}]; 92format_anno(Val, Ctxt) when is_atom(Val) -> 93 format_1(#c_literal{val=Val}, Ctxt); 94format_anno(Val, Ctxt) when is_integer(Val) -> 95 format_1(#c_literal{val=Val}, Ctxt). 96 97format_anno_list([H|[_|_]=T], Ctxt) -> 98 [format_anno(H, Ctxt), $, | format_anno_list(T, Ctxt)]; 99format_anno_list([H], Ctxt) -> 100 format_anno(H, Ctxt). 101 102strip_line([A | As]) when is_integer(A) -> 103 strip_line(As); 104strip_line([{A,C} | As]) when is_integer(A), is_integer(C) -> 105 strip_line(As); 106strip_line([{file,_File} | As]) -> 107 strip_line(As); 108strip_line([A | As]) -> 109 [A | strip_line(As)]; 110strip_line([]) -> 111 []. 112 113get_line([L | _As]) when is_integer(L) -> 114 L; 115get_line([{L, _Column} | _As]) when is_integer(L) -> 116 L; 117get_line([_ | As]) -> 118 get_line(As); 119get_line([]) -> 120 none. 121 122format(Node, Ctxt) -> 123 maybe_anno(Node, fun format_1/2, Ctxt). 124 125format_1(#c_literal{val=[]}, _) -> "[]"; 126format_1(#c_literal{val=I}, _) when is_integer(I) -> integer_to_list(I); 127format_1(#c_literal{val=F}, _) when is_float(F) -> float_to_list(F); 128format_1(#c_literal{val=A}, _) when is_atom(A) -> core_atom(A); 129format_1(#c_literal{val=[H|T]}, Ctxt) -> 130 format_1(#c_cons{hd=#c_literal{val=H},tl=#c_literal{val=T}}, Ctxt); 131format_1(#c_literal{val=Tuple}, Ctxt) when is_tuple(Tuple) -> 132 format_1(#c_tuple{es=[#c_literal{val=E} || E <- tuple_to_list(Tuple)]}, Ctxt); 133format_1(#c_literal{anno=A,val=Bitstring}, Ctxt) when is_bitstring(Bitstring) -> 134 Segs = segs_from_bitstring(Bitstring), 135 format_1(#c_binary{anno=A,segments=Segs}, Ctxt); 136format_1(#c_literal{anno=A,val=M},Ctxt) when is_map(M) -> 137 Pairs = maps:to_list(M), 138 Op = #c_literal{val=assoc}, 139 Cpairs = [#c_map_pair{op=Op, 140 key=#c_literal{val=K}, 141 val=#c_literal{val=V}} || {K,V} <- Pairs], 142 format_1(#c_map{anno=A,arg=#c_literal{val=#{}},es=Cpairs},Ctxt); 143format_1(#c_literal{val=F},_Ctxt) when is_function(F) -> 144 {module,M} = erlang:fun_info(F, module), 145 {name,N} = erlang:fun_info(F, name), 146 {arity,A} = erlang:fun_info(F, arity), 147 ["fun ",core_atom(M),$:,core_atom(N),$/,integer_to_list(A)]; 148format_1(#c_var{name={I,A}}, _) -> 149 [core_atom(I),$/,integer_to_list(A)]; 150format_1(#c_var{name=V}, _) -> 151 %% Internal variable names may be: 152 %% - atoms representing proper Erlang variable names, or 153 %% any atoms that may be printed without single-quoting 154 %% - nonnegative integers. 155 %% It is important that when printing variables, no two names 156 %% should ever map to the same string. 157 if is_atom(V) -> 158 S = atom_to_list(V), 159 case S of 160 [C | _] when C >= $A, C =< $Z -> 161 %% Ordinary uppercase-prefixed names are 162 %% printed just as they are. 163 S; 164 [$_ | _] -> 165 %% Already "_"-prefixed names are prefixed 166 %% with "_X", e.g. '_foo' => '_X_foo', to 167 %% avoid generating things like "____foo" upon 168 %% repeated writing and reading of code. 169 %% ("_X_X_X_foo" is better.) 170 [$_, $X | S]; 171 _ -> 172 %% Plain atoms are prefixed with a single "_". 173 %% E.g. foo => "_foo". 174 [$_ | S] 175 end; 176 is_integer(V) -> 177 %% Integers are also simply prefixed with "_". 178 [$_ | integer_to_list(V)] 179 end; 180format_1(#c_binary{segments=Segs}, Ctxt) -> 181 ["#{", 182 format_vseq(Segs, "", ",", add_indent(Ctxt, 2), 183 fun format_bitstr/2), 184 "}#" 185 ]; 186format_1(#c_tuple{es=Es}, Ctxt) -> 187 [${, 188 format_hseq(Es, ",", add_indent(Ctxt, 1), fun format/2), 189 $} 190 ]; 191format_1(#c_map{arg=#c_literal{val=M},es=Es}, Ctxt) 192 when is_map(M), map_size(M) =:= 0 -> 193 ["~{", 194 format_hseq(Es, ",", add_indent(Ctxt, 1), fun format/2), 195 "}~" 196 ]; 197format_1(#c_map{arg=Var,es=Es}, Ctxt) -> 198 ["~{", 199 format_hseq(Es, ",", add_indent(Ctxt, 1), fun format/2), 200 "|",format(Var, add_indent(Ctxt, 1)), 201 "}~" 202 ]; 203format_1(#c_map_pair{op=#c_literal{val=assoc},key=K,val=V}, Ctxt) -> 204 format_map_pair("=>", K, V, Ctxt); 205format_1(#c_map_pair{op=#c_literal{val=exact},key=K,val=V}, Ctxt) -> 206 format_map_pair(":=", K, V, Ctxt); 207format_1(#c_cons{hd=H,tl=T}, Ctxt) -> 208 Txt = ["["|format(H, add_indent(Ctxt, 1))], 209 [Txt|format_list_tail(T, add_indent(Ctxt, width(Txt, Ctxt)))]; 210format_1(#c_values{es=Es}, Ctxt) -> 211 format_values(Es, Ctxt); 212format_1(#c_alias{var=V,pat=P}, Ctxt) -> 213 Txt = [format(V, Ctxt)|" = "], 214 [Txt|format(P, add_indent(Ctxt, width(Txt, Ctxt)))]; 215format_1(#c_let{anno=Anno0,vars=Vs0,arg=A0,body=B}, #ctxt{clean=Clean}=Ctxt) -> 216 {Vs,A,Anno} = case Clean of 217 false -> 218 {Vs0,A0,Anno0}; 219 true -> 220 {[cerl:set_ann(V, []) || V <- Vs0], 221 clean_anno_carefully(A0), 222 []} 223 end, 224 case is_simple_term(A) andalso Anno =:= [] of 225 false -> 226 Ctxt1 = add_indent(Ctxt, Ctxt#ctxt.body_indent), 227 ["let ", 228 format_values(Vs, add_indent(Ctxt, 4)), 229 " =", 230 nl_indent(Ctxt1), 231 format(A, Ctxt1), 232 nl_indent(Ctxt), 233 "in " 234 | format(B, add_indent(Ctxt, 4)) 235 ]; 236 true -> 237 Ctxt1 = add_indent(Ctxt, Ctxt#ctxt.body_indent), 238 ["let ", 239 format_values(Vs, add_indent(Ctxt, 4)), 240 " = ", 241 format(A, Ctxt1), 242 nl_indent(Ctxt), 243 "in " 244 | format(B, add_indent(Ctxt, 4)) 245 ] 246 end; 247format_1(#c_letrec{defs=Fs,body=B}, Ctxt) -> 248 Ctxt1 = add_indent(Ctxt, Ctxt#ctxt.body_indent), 249 ["letrec", 250 nl_indent(Ctxt1), 251 format_funcs(Fs, Ctxt1), 252 nl_indent(Ctxt), 253 "in " 254 | format(B, add_indent(Ctxt, 4)) 255 ]; 256format_1(#c_seq{arg=A,body=B}, Ctxt) -> 257 Ctxt1 = add_indent(Ctxt, 4), 258 ["do ", 259 format(A, Ctxt1), 260 nl_indent(Ctxt1) 261 | format(B, Ctxt1) 262 ]; 263format_1(#c_case{arg=A,clauses=Cs}, Ctxt) -> 264 Ctxt1 = add_indent(Ctxt, Ctxt#ctxt.item_indent), 265 ["case ", 266 format(A, add_indent(Ctxt, 5)), 267 " of", 268 nl_indent(Ctxt1), 269 format_clauses(Cs, Ctxt1), 270 nl_indent(Ctxt) 271 | "end" 272 ]; 273format_1(#c_receive{clauses=Cs,timeout=T,action=A}, Ctxt) -> 274 Ctxt1 = add_indent(Ctxt, Ctxt#ctxt.item_indent), 275 ["receive", 276 nl_indent(Ctxt1), 277 format_clauses(Cs, Ctxt1), 278 nl_indent(Ctxt), 279 "after ", 280 format(T, add_indent(Ctxt, 6)), 281 " ->", 282 nl_indent(Ctxt1), 283 format(A, Ctxt1) 284 ]; 285format_1(#c_fun{vars=Vs,body=B}, Ctxt) -> 286 Ctxt1 = add_indent(Ctxt, Ctxt#ctxt.body_indent), 287 ["fun (", 288 format_hseq(Vs, ",", add_indent(Ctxt, 5), fun format/2), 289 ") ->", 290 nl_indent(Ctxt1) 291 | format(B, Ctxt1) 292 ]; 293format_1(#c_apply{op=O,args=As}, Ctxt0) -> 294 Ctxt1 = add_indent(Ctxt0, 6), %"apply " 295 Op = format(O, Ctxt1), 296 Ctxt2 = add_indent(Ctxt0, 4), 297 ["apply ",Op, 298 nl_indent(Ctxt2), 299 $(,format_hseq(As, ", ", add_indent(Ctxt2, 1), fun format/2),$) 300 ]; 301format_1(#c_call{module=M,name=N,args=As}, Ctxt0) -> 302 Ctxt1 = add_indent(Ctxt0, 5), %"call " 303 Mod = format(M, Ctxt1), 304 Ctxt2 = add_indent(Ctxt1, width(Mod, Ctxt1)+1), 305 Name = format(N, Ctxt2), 306 Ctxt3 = add_indent(Ctxt0, 4), 307 ["call ",Mod,":",Name, 308 nl_indent(Ctxt3), 309 $(,format_hseq(As, ", ", add_indent(Ctxt3, 1), fun format/2),$) 310 ]; 311format_1(#c_primop{name=N,args=As}, Ctxt0) -> 312 Ctxt1 = add_indent(Ctxt0, 7), %"primop " 313 Name = format(N, Ctxt1), 314 Ctxt2 = add_indent(Ctxt0, 4), 315 ["primop ",Name, 316 nl_indent(Ctxt2), 317 $(,format_hseq(As, ", ", add_indent(Ctxt2, 1), fun format/2),$) 318 ]; 319format_1(#c_catch{body=B}, Ctxt) -> 320 Ctxt1 = add_indent(Ctxt, Ctxt#ctxt.body_indent), 321 ["catch", 322 nl_indent(Ctxt1), 323 format(B, Ctxt1) 324 ]; 325format_1(#c_try{arg=E,vars=Vs,body=B,evars=Evs,handler=H}, Ctxt) -> 326 Ctxt1 = add_indent(Ctxt, Ctxt#ctxt.body_indent), 327 ["try", 328 nl_indent(Ctxt1), 329 format(E, Ctxt1), 330 nl_indent(Ctxt), 331 "of ", 332 format_values(Vs, add_indent(Ctxt, 3)), 333 " ->", 334 nl_indent(Ctxt1), 335 format(B, Ctxt1), 336 nl_indent(Ctxt), 337 "catch ", 338 format_values(Evs, add_indent(Ctxt, 6)), 339 " ->", 340 nl_indent(Ctxt1) 341 | format(H, Ctxt1) 342 ]; 343format_1(#c_module{name=N,exports=Es,attrs=As,defs=Ds}, Ctxt) -> 344 Mod = ["module ", format(N, Ctxt)], 345 [Mod," [", 346 format_vseq(Es, 347 "", ",", 348 add_indent(Ctxt, width(Mod, Ctxt)+2), 349 fun format/2), 350 "]", 351 nl_indent(Ctxt), 352 " attributes [", 353 format_vseq(As, 354 "", ",", 355 add_indent(Ctxt, 16), 356 fun format_def/2), 357 "]", 358 nl_indent(Ctxt), 359 format_funcs(Ds, Ctxt), 360 nl_indent(Ctxt) 361 | "end" 362 ]. 363 364format_funcs(Fs, Ctxt) -> 365 format_vseq(Fs, 366 "", "", 367 Ctxt, 368 fun format_def/2). 369 370format_def({N,V}, Ctxt0) -> 371 Ctxt1 = add_indent(Ctxt0, Ctxt0#ctxt.body_indent), 372 [format(N, Ctxt0), 373 " =", 374 nl_indent(Ctxt1) 375 | format(V, Ctxt1) 376 ]. 377 378 379format_values(Vs, Ctxt) -> 380 [$<, 381 format_hseq(Vs, ",", add_indent(Ctxt, 1), fun format/2), 382 $>]. 383 384format_bitstr(Node, Ctxt) -> 385 maybe_anno(Node, fun do_format_bitstr/2, Ctxt). 386 387do_format_bitstr(#c_bitstr{val=V,size=S,unit=U,type=T,flags=Fs}, Ctxt0) -> 388 Vs = [S, U, T, Fs], 389 Ctxt1 = add_indent(Ctxt0, 2), 390 Val = format(V, Ctxt1), 391 Ctxt2 = add_indent(Ctxt1, width(Val, Ctxt1) + 2), 392 ["#<", Val, ">(", format_hseq(Vs,",", Ctxt2, fun format/2), $)]. 393 394format_clauses(Cs, Ctxt) -> 395 format_vseq(Cs, "", "", Ctxt, fun format_clause/2). 396 397format_clause(Node, Ctxt) -> 398 maybe_anno(Node, fun format_clause_1/2, Ctxt). 399 400format_clause_1(#c_clause{pats=Ps,guard=G,body=B}, Ctxt) -> 401 Ptxt = format_values(Ps, Ctxt), 402 Ctxt2 = add_indent(Ctxt, Ctxt#ctxt.body_indent), 403 [Ptxt, 404 case is_trivial_guard(G) of 405 true -> 406 [" when ", 407 format_guard(G, add_indent(Ctxt, width(Ptxt, Ctxt) + 6))]; 408 false -> 409 [nl_indent(Ctxt2), "when ", 410 format_guard(G, add_indent(Ctxt2, 2))] 411 end++ 412 " ->", 413 nl_indent(Ctxt2) | format(B, Ctxt2) 414 ]. 415 416is_trivial_guard(#c_literal{val=Val}) when is_atom(Val) -> true; 417is_trivial_guard(_) -> false. 418 419format_guard(Node, Ctxt) -> 420 maybe_anno(Node, fun format_guard_1/2, Ctxt). 421 422format_guard_1(#c_call{module=M,name=N,args=As}, Ctxt0) -> 423 Ctxt1 = add_indent(Ctxt0, 5), %"call " 424 Mod = format(M, Ctxt1), 425 Ctxt2 = add_indent(Ctxt1, width(Mod, Ctxt1)+1), 426 Name = format(N, Ctxt2), 427 Ctxt3 = add_indent(Ctxt0, 4), 428 ["call ",Mod,":",Name, 429 nl_indent(Ctxt3), 430 $(,format_vseq(As, "",",", add_indent(Ctxt3, 1), fun format_guard/2),$) 431 ]; 432format_guard_1(E, Ctxt) -> format_1(E, Ctxt). %Anno already done 433 434%% format_hseq([Thing], Separator, Context, Fun) -> Txt. 435%% Format a sequence horizontally on the same line with Separator between. 436 437format_hseq([H], _, Ctxt, Fun) -> 438 Fun(H, Ctxt); 439format_hseq([H|T], Sep, Ctxt, Fun) -> 440 Txt = [Fun(H, Ctxt)|Sep], 441 Ctxt1 = add_indent(Ctxt, width(Txt, Ctxt)), 442 [Txt|format_hseq(T, Sep, Ctxt1, Fun)]; 443format_hseq([], _, _, _) -> "". 444 445%% format_vseq([Thing], LinePrefix, LineSuffix, Context, Fun) -> Txt. 446%% Format a sequence vertically in indented lines adding LinePrefix 447%% to the beginning of each line and LineSuffix to the end of each 448%% line. No prefix on the first line or suffix on the last line. 449 450format_vseq([H], _Pre, _Suf, Ctxt, Fun) -> 451 Fun(H, Ctxt); 452format_vseq([H|T], Pre, Suf, Ctxt, Fun) -> 453 [Fun(H, Ctxt),Suf,nl_indent(Ctxt),Pre| 454 format_vseq(T, Pre, Suf, Ctxt, Fun)]; 455format_vseq([], _, _, _, _) -> "". 456 457format_list_tail(#c_literal{anno=[],val=[]}, _) -> "]"; 458format_list_tail(#c_cons{anno=[],hd=H,tl=T}, Ctxt) -> 459 Txt = [$,|format(H, Ctxt)], 460 Ctxt1 = add_indent(Ctxt, width(Txt, Ctxt)), 461 [Txt|format_list_tail(T, Ctxt1)]; 462format_list_tail(Tail, Ctxt) -> 463 ["|",format(Tail, add_indent(Ctxt, 1)),"]"]. 464 465format_map_pair(Op, K, V, Ctxt0) -> 466 Ctxt1 = add_indent(Ctxt0, 1), 467 Txt = format(K, Ctxt1), 468 Ctxt2 = add_indent(Ctxt0, width(Txt, Ctxt1)), 469 [Txt,Op,format(V, Ctxt2)]. 470 471indent(#ctxt{indent=N}) -> 472 if 473 N =< 0 -> 474 ""; 475 true -> 476 lists:duplicate(N div ?TAB_WIDTH, $\t) ++ spaces(N rem ?TAB_WIDTH) 477 end. 478 479nl_indent(Ctxt) -> [$\n|indent(Ctxt)]. 480 481spaces(0) -> ""; 482spaces(1) -> " "; 483spaces(2) -> " "; 484spaces(3) -> " "; 485spaces(4) -> " "; 486spaces(5) -> " "; 487spaces(6) -> " "; 488spaces(7) -> " ". 489 490%% Undo indentation done by nl_indent/1. 491unindent(T, Ctxt) -> 492 unindent(T, Ctxt#ctxt.indent, []). 493 494unindent(T, N, C) when N =< 0 -> 495 [T|C]; 496unindent([$\s|T], N, C) -> 497 unindent(T, N - 1, C); 498unindent([$\t|T], N, C) -> 499 Tab = ?TAB_WIDTH, 500 if N >= Tab -> 501 unindent(T, N - Tab, C); 502 true -> 503 unindent([spaces(Tab - N)|T], 0, C) 504 end; 505unindent([L|T], N, C) when is_list(L) -> 506 unindent(L, N, [T|C]). 507 508 509width(Txt, Ctxt) -> 510 width(Txt, 0, Ctxt, []). 511 512width([$\t|T], A, Ctxt, C) -> 513 width(T, A + ?TAB_WIDTH, Ctxt, C); 514width([$\n|T], _, Ctxt, C) -> 515 width(unindent([T|C], Ctxt), Ctxt); 516width([H|T], A, Ctxt, C) when is_list(H) -> 517 width(H, A, Ctxt, [T|C]); 518width([_|T], A, Ctxt, C) -> 519 width(T, A + 1, Ctxt, C); 520width([], A, Ctxt, [H|T]) -> 521 width(H, A, Ctxt, T); 522width([], A, _, []) -> A. 523 524add_indent(Ctxt, Dx) -> 525 Ctxt#ctxt{indent = Ctxt#ctxt.indent + Dx}. 526 527core_atom(A) -> io_lib:write_string(atom_to_list(A), $'). 528 529 530is_simple_term(#c_tuple{es=Es}) -> 531 length(Es) < 4 andalso lists:all(fun is_simple_term/1, Es); 532is_simple_term(#c_var{}) -> true; 533is_simple_term(#c_literal{val=[_|_]}) -> false; 534is_simple_term(#c_literal{val=V}) -> not is_tuple(V); 535is_simple_term(_) -> false. 536 537segs_from_bitstring(<<H,T/bitstring>>) -> 538 [#c_bitstr{val=#c_literal{val=H}, 539 size=#c_literal{val=8}, 540 unit=#c_literal{val=1}, 541 type=#c_literal{val=integer}, 542 flags=#c_literal{val=[unsigned,big]}}|segs_from_bitstring(T)]; 543segs_from_bitstring(<<>>) -> 544 []; 545segs_from_bitstring(Bitstring) -> 546 N = bit_size(Bitstring), 547 <<I:N>> = Bitstring, 548 [#c_bitstr{val=#c_literal{val=I}, 549 size=#c_literal{val=N}, 550 unit=#c_literal{val=1}, 551 type=#c_literal{val=integer}, 552 flags=#c_literal{val=[unsigned,big]}}]. 553 554clean_anno_carefully(Node) -> 555 Anno = clean_anno_carefully_1(cerl:get_ann(Node)), 556 cerl:set_ann(Node, Anno). 557 558clean_anno_carefully_1([letrec_goto=Keep|Annos]) -> 559 [Keep|clean_anno_carefully_1(Annos)]; 560clean_anno_carefully_1([_|Annos]) -> 561 clean_anno_carefully_1(Annos); 562clean_anno_carefully_1([]) -> []. 563