-module(glam@doc). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -export([append/2, break/2, concat/1, append_docs/2, flex_break/2, force_break/1, from_string/1, group/1, join/2, concat_join/2, lines/1, nest/2, nest_docs/2, prepend/2, prepend_docs/2, to_string_builder/2, to_string/2]). -export_type([document/0, mode/0]). -opaque document() :: {line, integer()} | {concat, list(document())} | {text, binary(), integer()} | {nest, document(), integer()} | {force_break, document()} | {break, binary(), binary()} | {flex_break, binary(), binary()} | {group, document()}. -type mode() :: broken | force_broken | unbroken. -spec append(document(), document()) -> document(). append(First, Second) -> case First of {concat, Docs} -> {concat, gleam@list:append(Docs, [Second])}; _ -> {concat, [First, Second]} end. -spec break(binary(), binary()) -> document(). break(Unbroken, Broken) -> {break, Unbroken, Broken}. -spec concat(list(document())) -> document(). concat(Docs) -> {concat, Docs}. -spec append_docs(document(), list(document())) -> document(). append_docs(First, Docs) -> append(First, concat(Docs)). -spec flex_break(binary(), binary()) -> document(). flex_break(Unbroken, Broken) -> {flex_break, Unbroken, Broken}. -spec force_break(document()) -> document(). force_break(Doc) -> {force_break, Doc}. -spec from_string(binary()) -> document(). from_string(String) -> {text, String, gleam@string:length(String)}. -spec group(document()) -> document(). group(Doc) -> {group, Doc}. -spec join(list(document()), document()) -> document(). join(Docs, Separator) -> concat(gleam@list:intersperse(Docs, Separator)). -spec concat_join(list(document()), list(document())) -> document(). concat_join(Docs, Separators) -> join(Docs, concat(Separators)). -spec lines(integer()) -> document(). lines(Size) -> {line, Size}. -spec nest(document(), integer()) -> document(). nest(Doc, Indentation) -> {nest, Doc, Indentation}. -spec nest_docs(list(document()), integer()) -> document(). nest_docs(Docs, Indentation) -> {nest, concat(Docs), Indentation}. -spec prepend(document(), document()) -> document(). prepend(First, Second) -> case First of {concat, Docs} -> {concat, [Second | Docs]}; _ -> {concat, [Second, First]} end. -spec prepend_docs(document(), list(document())) -> document(). prepend_docs(First, Docs) -> prepend(First, concat(Docs)). -spec fits(list({integer(), mode(), document()}), integer(), integer()) -> boolean(). fits(Docs, Max_width, Current_width) -> case Docs of _ when Current_width > Max_width -> false; [] -> true; [{Indent, Mode, Doc} | Rest] -> case Doc of {line, _} -> true; {force_break, _} -> false; {text, _, Length} -> fits(Rest, Max_width, Current_width + Length); {nest, Doc@1, I} -> _pipe = [{Indent + I, Mode, Doc@1} | Rest], fits(_pipe, Max_width, Current_width); {break, Unbroken, _} -> case Mode of broken -> true; force_broken -> true; unbroken -> fits( Rest, Max_width, Current_width + gleam@string:length(Unbroken) ) end; {flex_break, Unbroken, _} -> case Mode of broken -> true; force_broken -> true; unbroken -> fits( Rest, Max_width, Current_width + gleam@string:length(Unbroken) ) end; {group, Doc@2} -> fits( [{Indent, Mode, Doc@2} | Rest], Max_width, Current_width ); {concat, Docs@1} -> _pipe@1 = gleam@list:map( Docs@1, fun(Doc@3) -> {Indent, Mode, Doc@3} end ), _pipe@2 = gleam@list:append(_pipe@1, Rest), fits(_pipe@2, Max_width, Current_width) end end. -spec indentation(integer()) -> binary(). indentation(Size) -> gleam@string:repeat(<<" "/utf8>>, Size). -spec do_format( gleam@string_builder:string_builder(), integer(), integer(), list({integer(), mode(), document()}) ) -> gleam@string_builder:string_builder(). do_format(Acc, Max_width, Current_width, Docs) -> case Docs of [] -> Acc; [{Indent, Mode, Doc} | Rest] -> case Doc of {line, Size} -> _pipe = gleam@string_builder:append( Acc, gleam@string:repeat(<<"\n"/utf8>>, Size) ), _pipe@1 = gleam@string_builder:append( _pipe, indentation(Indent) ), do_format(_pipe@1, Max_width, Indent, Rest); {flex_break, Unbroken, Broken} -> New_unbroken_width = Current_width + gleam@string:length( Unbroken ), case fits(Rest, Max_width, New_unbroken_width) of true -> _pipe@2 = gleam@string_builder:append(Acc, Unbroken), do_format( _pipe@2, Max_width, New_unbroken_width, Rest ); false -> _pipe@3 = gleam@string_builder:append(Acc, Broken), _pipe@4 = gleam@string_builder:append( _pipe@3, <<"\n"/utf8>> ), _pipe@5 = gleam@string_builder:append( _pipe@4, indentation(Indent) ), do_format(_pipe@5, Max_width, Indent, Rest) end; {break, Unbroken@1, Broken@1} -> case Mode of unbroken -> New_width = Current_width + gleam@string:length( Unbroken@1 ), _pipe@6 = gleam@string_builder:append( Acc, Unbroken@1 ), do_format(_pipe@6, Max_width, New_width, Rest); broken -> _pipe@7 = gleam@string_builder:append(Acc, Broken@1), _pipe@8 = gleam@string_builder:append( _pipe@7, <<"\n"/utf8>> ), _pipe@9 = gleam@string_builder:append( _pipe@8, indentation(Indent) ), do_format(_pipe@9, Max_width, Indent, Rest); force_broken -> _pipe@7 = gleam@string_builder:append(Acc, Broken@1), _pipe@8 = gleam@string_builder:append( _pipe@7, <<"\n"/utf8>> ), _pipe@9 = gleam@string_builder:append( _pipe@8, indentation(Indent) ), do_format(_pipe@9, Max_width, Indent, Rest) end; {force_break, Doc@1} -> _pipe@10 = [{Indent, force_broken, Doc@1} | Rest], do_format(Acc, Max_width, Current_width, _pipe@10); {concat, Docs@1} -> _pipe@11 = gleam@list:map( Docs@1, fun(Doc@2) -> {Indent, Mode, Doc@2} end ), _pipe@12 = gleam@list:append(_pipe@11, Rest), do_format(Acc, Max_width, Current_width, _pipe@12); {group, Doc@3} -> _pipe@13 = case fits( [{Indent, unbroken, Doc@3}], Max_width, Current_width ) of true -> {Indent, unbroken, Doc@3}; false -> {Indent, broken, Doc@3} end, _pipe@14 = gleam@list:prepend(Rest, _pipe@13), do_format(Acc, Max_width, Current_width, _pipe@14); {nest, Doc@4, I} -> _pipe@15 = [{Indent + I, Mode, Doc@4} | Rest], do_format(Acc, Max_width, Current_width, _pipe@15); {text, Text, Length} -> _pipe@16 = gleam@string_builder:append(Acc, Text), do_format(_pipe@16, Max_width, Current_width + Length, Rest) end end. -spec to_string_builder(document(), integer()) -> gleam@string_builder:string_builder(). to_string_builder(Doc, Width) -> do_format(gleam@string_builder:new(), Width, 0, [{0, unbroken, Doc}]). -spec to_string(document(), integer()) -> binary(). to_string(Doc, Width) -> _pipe = to_string_builder(Doc, Width), gleam@string_builder:to_string(_pipe).