-module(commonmark@internal@parser). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -export([trim_indent/2, parse_text/1, parse_block_state/1, parse_blocks/1]). -export_type([block_state/0, inline_state/0, block_parse_state/0]). -type block_state() :: outside_block | {paragraph_builder, list(binary())} | {fenced_code_block_builder, binary(), gleam@option:option(binary()), gleam@option:option(binary()), list(binary()), integer()} | {indented_code_block_builder, list(binary())}. -type inline_state() :: {text_accumulator, list(binary())} | {autolink_accumulator, list(binary())}. -opaque block_parse_state() :: {paragraph, binary()} | horizontal_break | {heading, integer(), gleam@option:option(binary())} | {code_block, gleam@option:option(binary()), gleam@option:option(binary()), binary()}. -spec do_trim_indent(binary(), integer(), integer()) -> binary(). do_trim_indent(Line, N, Removed) -> case Line of _ when Removed >= N -> Line; <<" "/utf8, Rest/binary>> -> do_trim_indent(Rest, N, Removed + 1); <<"\t"/utf8, Rest@1/binary>> -> Next_tab_stop = Removed + (4 - (Removed rem 4)), do_trim_indent(Rest@1, N, Removed + Next_tab_stop); _ -> Line end. -spec trim_indent(binary(), integer()) -> binary(). trim_indent(Line, N) -> do_trim_indent(Line, N, 0). -spec determine_indent(gleam@option:option(binary())) -> integer(). determine_indent(Indent) -> case Indent of none -> 0; {some, S} -> gleam@string:length(S) end. -spec parse_autolink(binary()) -> commonmark@ast:inline_node(). parse_autolink(Href) -> _assert_subject = gleam@regex:from_string( <<"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"/utf8>> ), {ok, Email_regex} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"commonmark/internal/parser"/utf8>>, function => <<"parse_autolink"/utf8>>, line => 63}) end, _assert_subject@1 = gleam@regex:from_string( <<"^[a-zA-Z][-a-zA-Z+.]{1,31}:"/utf8>> ), {ok, Uri_regex} = case _assert_subject@1 of {ok, _} -> _assert_subject@1; _assert_fail@1 -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@1, module => <<"commonmark/internal/parser"/utf8>>, function => <<"parse_autolink"/utf8>>, line => 67}) end, case {gleam@regex:check(Email_regex, Href), gleam@regex:check(Uri_regex, Href)} of {true, _} -> {email_autolink, Href}; {_, true} -> {uri_autolink, Href}; {false, false} -> {text, <<<<"<"/utf8, Href/binary>>/binary, ">"/utf8>>} end. -spec do_parse_text( list(binary()), inline_state(), list(commonmark@ast:inline_node()) ) -> list(commonmark@ast:inline_node()). do_parse_text(Text, State, Acc) -> case {State, Text} of {{autolink_accumulator, Ts}, []} -> [{text, begin _pipe = [<<"<"/utf8>> | lists:reverse(Ts)], gleam@string:join(_pipe, <<""/utf8>>) end} | Acc]; {{text_accumulator, Ts@1}, []} -> _pipe@4 = [{text, begin _pipe@1 = Ts@1, _pipe@2 = lists:reverse(_pipe@1), _pipe@3 = gleam@string:join(_pipe@2, <<""/utf8>>), gleam@string:trim(_pipe@3) end} | Acc], lists:reverse(_pipe@4); {{text_accumulator, Ts@2}, [<<" "/utf8>>, <<" "/utf8>>, <<"\n"/utf8>> | Gs]} -> do_parse_text( Gs, {text_accumulator, []}, [hard_line_break, {text, begin _pipe@5 = Ts@2, _pipe@6 = lists:reverse(_pipe@5), _pipe@7 = gleam@string:join(_pipe@6, <<""/utf8>>), gleam@string:trim(_pipe@7) end} | Acc] ); {{text_accumulator, Ts@2}, [<<"\\"/utf8>>, <<"\n"/utf8>> | Gs]} -> do_parse_text( Gs, {text_accumulator, []}, [hard_line_break, {text, begin _pipe@5 = Ts@2, _pipe@6 = lists:reverse(_pipe@5), _pipe@7 = gleam@string:join(_pipe@6, <<""/utf8>>), gleam@string:trim(_pipe@7) end} | Acc] ); {{autolink_accumulator, Ts@3}, [<<" "/utf8>>, <<" "/utf8>>, <<"\n"/utf8>> | Gs@1]} -> do_parse_text( Gs@1, {text_accumulator, []}, [hard_line_break, {text, begin _pipe@8 = lists:reverse([<<"<"/utf8>> | Ts@3]), gleam@string:join(_pipe@8, <<""/utf8>>) end} | Acc] ); {{autolink_accumulator, Ts@3}, [<<"\\"/utf8>>, <<"\n"/utf8>> | Gs@1]} -> do_parse_text( Gs@1, {text_accumulator, []}, [hard_line_break, {text, begin _pipe@8 = lists:reverse([<<"<"/utf8>> | Ts@3]), gleam@string:join(_pipe@8, <<""/utf8>>) end} | Acc] ); {{text_accumulator, Ts@4}, [<<"\n"/utf8>> | Gs@2]} -> do_parse_text( Gs@2, {text_accumulator, []}, [soft_line_break, {text, begin _pipe@9 = Ts@4, _pipe@10 = lists:reverse(_pipe@9), _pipe@11 = gleam@string:join(_pipe@10, <<""/utf8>>), gleam@string:trim(_pipe@11) end} | Acc] ); {{autolink_accumulator, Ts@5}, [<<"\n"/utf8>> | Gs@3]} -> do_parse_text( Gs@3, {text_accumulator, []}, [soft_line_break, {text, begin _pipe@12 = lists:reverse([<<"<"/utf8>> | Ts@5]), gleam@string:join(_pipe@12, <<""/utf8>>) end} | Acc] ); {{text_accumulator, Ts@6}, [<<"<"/utf8>> | Gs@4]} -> do_parse_text( Gs@4, {autolink_accumulator, []}, [{text, begin _pipe@13 = Ts@6, _pipe@14 = lists:reverse(_pipe@13), gleam@string:join(_pipe@14, <<""/utf8>>) end} | Acc] ); {{autolink_accumulator, Ts@7}, [<<"\t"/utf8>> = Space | Gs@5]} -> do_parse_text( Gs@5, {text_accumulator, [Space | lists:append(Ts@7, [<<"<"/utf8>>])]}, Acc ); {{autolink_accumulator, Ts@7}, [<<" "/utf8>> = Space | Gs@5]} -> do_parse_text( Gs@5, {text_accumulator, [Space | lists:append(Ts@7, [<<"<"/utf8>>])]}, Acc ); {{autolink_accumulator, Ts@8}, [<<">"/utf8>> | Gs@6]} -> do_parse_text( Gs@6, {text_accumulator, []}, [parse_autolink( begin _pipe@15 = Ts@8, _pipe@16 = lists:reverse(_pipe@15), gleam@string:join(_pipe@16, <<""/utf8>>) end ) | Acc] ); {{autolink_accumulator, Ts@9}, [G | Gs@7]} -> do_parse_text(Gs@7, {autolink_accumulator, [G | Ts@9]}, Acc); {{text_accumulator, Ts@10}, [G@1 | Gs@8]} -> do_parse_text(Gs@8, {text_accumulator, [G@1 | Ts@10]}, Acc) end. -spec parse_text(binary()) -> list(commonmark@ast:inline_node()). parse_text(Text) -> _pipe = Text, _pipe@1 = gleam@string:to_graphemes(_pipe), do_parse_text(_pipe@1, {text_accumulator, []}, []). -spec parse_block_state(block_parse_state()) -> list(commonmark@ast:block_node()). parse_block_state(State) -> case State of {paragraph, Lines} -> [begin _pipe = Lines, _pipe@1 = parse_text(_pipe), {paragraph, _pipe@1} end]; {code_block, Info, Full_info, Lines@1} -> [{code_block, Info, Full_info, Lines@1}]; horizontal_break -> [horizontal_break]; {heading, Level, {some, Contents}} -> [{heading, Level, parse_text(Contents)}]; {heading, Level@1, none} -> [{heading, Level@1, []}] end. -spec do_parse_blocks(block_state(), list(block_parse_state()), list(binary())) -> list(block_parse_state()). do_parse_blocks(State, Acc, Lines) -> _assert_subject = gleam@regex:from_string( <<"^ {0,3}(?:\\*[* \t]*\\*[* \t]*\\*|-[- \t]*-[- \t]*-|_[_ \t]*_[_ \t]*_)[ \t]*$"/utf8>> ), {ok, Hr_regex} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"commonmark/internal/parser"/utf8>>, function => <<"do_parse_blocks"/utf8>>, line => 157}) end, _assert_subject@1 = gleam@regex:from_string( <<"^ {0,3}(#{1,6})([ \t]+.*?)?(?:(?<=[ \t])#*)?[ \t]*$"/utf8>> ), {ok, Atx_header_regex} = case _assert_subject@1 of {ok, _} -> _assert_subject@1; _assert_fail@1 -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@1, module => <<"commonmark/internal/parser"/utf8>>, function => <<"do_parse_blocks"/utf8>>, line => 161}) end, _assert_subject@2 = gleam@regex:from_string( <<"^ {0,3}([-=])+[ \t]*$"/utf8>> ), {ok, Setext_header_regex} = case _assert_subject@2 of {ok, _} -> _assert_subject@2; _assert_fail@2 -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@2, module => <<"commonmark/internal/parser"/utf8>>, function => <<"do_parse_blocks"/utf8>>, line => 163}) end, _assert_subject@3 = case State of {fenced_code_block_builder, Break, _, _, _, _} -> gleam@regex:from_string( <<<<"^ {0,3}"/utf8, Break/binary>>/binary, "+[ \t]*$"/utf8>> ); _ -> gleam@regex:from_string( <<"^( {0,3})([~`]{3,})[ \t]*(([^\\s]+).*?)?[ \t]*$"/utf8>> ) end, {ok, Fenced_code_regex} = case _assert_subject@3 of {ok, _} -> _assert_subject@3; _assert_fail@3 -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@3, module => <<"commonmark/internal/parser"/utf8>>, function => <<"do_parse_blocks"/utf8>>, line => 165}) end, _assert_subject@4 = gleam@regex:from_string( <<<<"^"/utf8, "(?: {0,3}\t| )"/utf8>>/binary, "|^[ \t]*$"/utf8>> ), {ok, Valid_indented_code_regex} = case _assert_subject@4 of {ok, _} -> _assert_subject@4; _assert_fail@4 -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@4, module => <<"commonmark/internal/parser"/utf8>>, function => <<"do_parse_blocks"/utf8>>, line => 170}) end, L = begin _pipe = gleam@list:first(Lines), gleam@result:unwrap(_pipe, <<""/utf8>>) end, case {State, Lines, gleam@regex:check(Hr_regex, L), gleam@regex:scan(Atx_header_regex, L), gleam@regex:scan(Setext_header_regex, L), gleam@regex:scan(Fenced_code_regex, L), gleam@regex:check(Valid_indented_code_regex, L)} of {{paragraph_builder, Lines@1}, [], _, _, _, _, _} -> _pipe@2 = [{paragraph, begin _pipe@1 = Lines@1, gleam@string:join(_pipe@1, <<"\n"/utf8>>) end} | Acc], lists:reverse(_pipe@2); {{indented_code_block_builder, Lines@2}, [], _, _, _, _, _} -> _pipe@8 = [{code_block, none, none, begin _pipe@5 = [<<""/utf8>> | begin _pipe@3 = Lines@2, _pipe@4 = gleam@list:map( _pipe@3, fun(_capture) -> trim_indent(_capture, 4) end ), gleam@list:drop_while( _pipe@4, fun(N) -> N =:= <<""/utf8>> end ) end], _pipe@6 = lists:reverse(_pipe@5), _pipe@7 = gleam@list:drop_while( _pipe@6, fun(N@1) -> N@1 =:= <<""/utf8>> end ), gleam@string:join(_pipe@7, <<"\n"/utf8>>) end} | Acc], lists:reverse(_pipe@8); {{fenced_code_block_builder, _, Info, Full_info, Contents, Indent}, [<<""/utf8>>], _, _, _, _, _} -> _pipe@12 = [{code_block, Info, Full_info, begin _pipe@9 = [<<""/utf8>> | Contents], _pipe@10 = gleam@list:map( _pipe@9, fun(_capture@1) -> trim_indent(_capture@1, Indent) end ), _pipe@11 = lists:reverse(_pipe@10), gleam@string:join(_pipe@11, <<"\n"/utf8>>) end} | Acc], lists:reverse(_pipe@12); {{fenced_code_block_builder, _, Info, Full_info, Contents, Indent}, [], _, _, _, _, _} -> _pipe@12 = [{code_block, Info, Full_info, begin _pipe@9 = [<<""/utf8>> | Contents], _pipe@10 = gleam@list:map( _pipe@9, fun(_capture@1) -> trim_indent(_capture@1, Indent) end ), _pipe@11 = lists:reverse(_pipe@10), gleam@string:join(_pipe@11, <<"\n"/utf8>>) end} | Acc], lists:reverse(_pipe@12); {outside_block, [], _, _, _, _, _} -> _pipe@13 = Acc, lists:reverse(_pipe@13); {{paragraph_builder, Bs}, [<<" "/utf8>> | Ls], _, _, _, _, _} -> do_parse_blocks( outside_block, [{paragraph, begin _pipe@14 = lists:reverse(Bs), gleam@string:join(_pipe@14, <<"\n"/utf8>>) end} | Acc], Ls ); {{paragraph_builder, Bs}, [<<"\\"/utf8>> | Ls], _, _, _, _, _} -> do_parse_blocks( outside_block, [{paragraph, begin _pipe@14 = lists:reverse(Bs), gleam@string:join(_pipe@14, <<"\n"/utf8>>) end} | Acc], Ls ); {{paragraph_builder, Bs}, [<<""/utf8>> | Ls], _, _, _, _, _} -> do_parse_blocks( outside_block, [{paragraph, begin _pipe@14 = lists:reverse(Bs), gleam@string:join(_pipe@14, <<"\n"/utf8>>) end} | Acc], Ls ); {outside_block, [<<" "/utf8>> | Ls@1], _, _, _, _, _} -> do_parse_blocks(outside_block, Acc, Ls@1); {outside_block, [<<"\\"/utf8>> | Ls@1], _, _, _, _, _} -> do_parse_blocks(outside_block, Acc, Ls@1); {outside_block, [<<""/utf8>> | Ls@1], _, _, _, _, _} -> do_parse_blocks(outside_block, Acc, Ls@1); {outside_block, [L@1 | Ls@2], _, _, _, _, true} -> do_parse_blocks({indented_code_block_builder, [L@1]}, Acc, Ls@2); {{indented_code_block_builder, Bs@1}, [L@2], _, _, _, _, true} -> do_parse_blocks( {indented_code_block_builder, [L@2 | Bs@1]}, Acc, [] ); {{indented_code_block_builder, Bs@2}, [L@3 | Ls@3], _, _, _, _, true} -> do_parse_blocks( {indented_code_block_builder, [L@3 | Bs@2]}, Acc, Ls@3 ); {{indented_code_block_builder, Bs@3}, Ls@4, _, _, _, _, false} -> do_parse_blocks( outside_block, [{code_block, none, none, begin _pipe@17 = [<<""/utf8>> | begin _pipe@15 = Bs@3, _pipe@16 = gleam@list:map( _pipe@15, fun(_capture@2) -> trim_indent(_capture@2, 4) end ), gleam@list:drop_while( _pipe@16, fun(N@2) -> N@2 =:= <<""/utf8>> end ) end], _pipe@18 = lists:reverse(_pipe@17), _pipe@19 = gleam@list:drop_while( _pipe@18, fun(N@3) -> N@3 =:= <<""/utf8>> end ), gleam@string:join(_pipe@19, <<"\n"/utf8>>) end} | Acc], Ls@4 ); {{paragraph_builder, Bs@4}, [_ | Ls@5], _, _, _, [{match, _, [Indent@1, {some, Exit}]}], _} -> do_parse_blocks( {fenced_code_block_builder, Exit, none, none, [], determine_indent(Indent@1)}, [{paragraph, begin _pipe@20 = lists:reverse(Bs@4), gleam@string:join(_pipe@20, <<"\n"/utf8>>) end} | Acc], Ls@5 ); {{paragraph_builder, Bs@5}, [_ | Ls@6], _, _, _, [{match, _, [Indent@2, {some, Exit@1}, Full_info@1, Info@1]}], _} -> do_parse_blocks( {fenced_code_block_builder, Exit@1, Info@1, Full_info@1, [], determine_indent(Indent@2)}, [{paragraph, begin _pipe@21 = lists:reverse(Bs@5), gleam@string:join(_pipe@21, <<"\n"/utf8>>) end} | Acc], Ls@6 ); {outside_block, [_ | Ls@7], _, _, _, [{match, _, [Indent@3, {some, Exit@2}]}], _} -> do_parse_blocks( {fenced_code_block_builder, Exit@2, none, none, [], determine_indent(Indent@3)}, Acc, Ls@7 ); {outside_block, [_ | Ls@8], _, _, _, [{match, _, [Indent@4, {some, Exit@3}, Full_info@2, Info@2]}], _} -> do_parse_blocks( {fenced_code_block_builder, Exit@3, Info@2, Full_info@2, [], determine_indent(Indent@4)}, Acc, Ls@8 ); {{fenced_code_block_builder, _, Info@3, Full_info@3, Bs@6, Indent@5}, [_ | Ls@9], _, _, _, [{match, _, _}], _} -> do_parse_blocks( outside_block, [{code_block, Info@3, Full_info@3, begin _pipe@23 = lists:reverse( [<<""/utf8>> | begin _pipe@22 = Bs@6, gleam@list:map( _pipe@22, fun(_capture@3) -> trim_indent( _capture@3, Indent@5 ) end ) end] ), gleam@string:join(_pipe@23, <<"\n"/utf8>>) end} | Acc], Ls@9 ); {{fenced_code_block_builder, Break@1, Info@4, Full_info@4, Bs@7, Indent@6}, [L@4 | Ls@10], _, _, _, _, _} -> do_parse_blocks( {fenced_code_block_builder, Break@1, Info@4, Full_info@4, [L@4 | Bs@7], Indent@6}, Acc, Ls@10 ); {{paragraph_builder, Bs@8}, [_ | Ls@11], _, _, [{match, _, [{some, <<"="/utf8>>}]}], _, _} -> do_parse_blocks( outside_block, [{heading, 1, {some, begin _pipe@24 = lists:reverse(Bs@8), gleam@string:join(_pipe@24, <<"\n"/utf8>>) end}} | Acc], Ls@11 ); {{paragraph_builder, Bs@9}, [_ | Ls@12], _, _, [{match, _, [{some, <<"-"/utf8>>}]}], _, _} -> do_parse_blocks( outside_block, [{heading, 2, {some, begin _pipe@25 = lists:reverse(Bs@9), gleam@string:join(_pipe@25, <<"\n"/utf8>>) end}} | Acc], Ls@12 ); {{paragraph_builder, Bs@10}, [_ | Ls@13], true, _, _, _, _} -> do_parse_blocks( outside_block, [horizontal_break, {paragraph, begin _pipe@26 = lists:reverse(Bs@10), gleam@string:join(_pipe@26, <<"\n"/utf8>>) end} | Acc], Ls@13 ); {outside_block, [_ | Ls@14], true, _, _, _, _} -> do_parse_blocks(outside_block, [horizontal_break | Acc], Ls@14); {outside_block, [_ | Ls@15], _, [{match, _, [{some, Heading}]}], _, _, _} -> do_parse_blocks( outside_block, [{heading, gleam@string:length(Heading), none} | Acc], Ls@15 ); {outside_block, [_ | Ls@16], _, [{match, _, [{some, Heading@1}, {some, Contents@1}]}], _, _, _} -> do_parse_blocks( outside_block, [{heading, gleam@string:length(Heading@1), {some, Contents@1}} | Acc], Ls@16 ); {{paragraph_builder, Bs@11}, [_ | Ls@17], _, [{match, _, [{some, Heading@2}]}], _, _, _} -> do_parse_blocks( outside_block, [{heading, gleam@string:length(Heading@2), none}, {paragraph, begin _pipe@27 = lists:reverse(Bs@11), gleam@string:join(_pipe@27, <<"\n"/utf8>>) end} | Acc], Ls@17 ); {{paragraph_builder, Bs@12}, [_ | Ls@18], _, [{match, _, [{some, Heading@3}, {some, Contents@2}]}], _, _, _} -> do_parse_blocks( outside_block, [{heading, gleam@string:length(Heading@3), {some, Contents@2}}, {paragraph, begin _pipe@28 = lists:reverse(Bs@12), gleam@string:join(_pipe@28, <<"\n"/utf8>>) end} | Acc], Ls@18 ); {outside_block, [Line | Ls@19], _, _, _, _, _} -> do_parse_blocks({paragraph_builder, [Line]}, Acc, Ls@19); {{paragraph_builder, Bs@13}, [Line@1 | Ls@20], _, _, _, _, _} -> do_parse_blocks({paragraph_builder, [Line@1 | Bs@13]}, Acc, Ls@20) end. -spec parse_blocks(list(binary())) -> list(block_parse_state()). parse_blocks(Lines) -> do_parse_blocks(outside_block, [], Lines).