Skip to main content

src/swatch.erl

-module(swatch).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/swatch.gleam").
-export([to_tokens/1, token_to_source/1, to_source/1, tokens_to_html/1, to_html/1, tokens_to_ansi/1, to_ansi/1]).
-export_type([token/0, attribute_head/0, mode/0, parenthesis/0, state/0, at_rule_context/0, attribute_position/0]).

-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.

?MODULEDOC(
    " Swatch is a CSS syntax highlighter.\n"
    "\n"
    " Use [`to_tokens`](#to_tokens) for classified tokens, or\n"
    " [`to_html`](#to_html) / [`to_ansi`](#to_ansi) to render directly.\n"
    "\n"
    " Based on CSS Syntax Level 3.\n"
).

-type token() :: {whitespace, binary()} |
    {comment, binary()} |
    {selector, binary()} |
    {class_selector, binary()} |
    {id_selector, binary()} |
    {pseudo_selector, binary()} |
    {attribute_name, binary()} |
    {attribute_value, binary()} |
    {attribute_flag, binary()} |
    {at_rule, binary()} |
    {property, binary()} |
    {variable, binary()} |
    {string, binary()} |
    {number, binary()} |
    {unit, binary()} |
    {hex_color, binary()} |
    {function, binary()} |
    {keyword, binary()} |
    {important, binary()} |
    {operator, binary()} |
    {punctuation, binary()} |
    {other, binary()}.

-type attribute_head() :: {head_emit,
        list(token()),
        list({swatch@internal@lexer:lex_token(), boolean()}),
        attribute_position()} |
    {head_close, list({swatch@internal@lexer:lex_token(), boolean()})} |
    head_other.

-type mode() :: selector_mode | property_mode | value_mode | at_rule_mode.

-type parenthesis() :: grouping | {function_call, mode()}.

-type state() :: {state, mode(), list(mode()), list(parenthesis()), binary()}.

-type at_rule_context() :: {at_rule_context, mode(), list({binary(), mode()})}.

-type attribute_position() :: before_matcher |
    after_matcher |
    after_value |
    after_flag.

-file("src/swatch.gleam", 760).
-spec initial_state() -> state().
initial_state() ->
    {state, selector_mode, [], [], <<""/utf8>>}.

-file("src/swatch.gleam", 577).
-spec important_match_loop(
    list({swatch@internal@lexer:lex_token(), boolean()}),
    binary()
) -> {ok, {binary(), list({swatch@internal@lexer:lex_token(), boolean()})}} |
    {error, nil}.
important_match_loop(Tokens, Acc) ->
    case Tokens of
        [{{lex_whitespace, S}, _} | Rest] ->
            important_match_loop(Rest, <<Acc/binary, S/binary>>);

        [{{lex_comment, S@1}, _} | Rest@1] ->
            important_match_loop(Rest@1, <<Acc/binary, S@1/binary>>);

        [{{lex_ident, Name}, _} | Rest@2] ->
            case string:lowercase(Name) =:= <<"important"/utf8>> of
                true ->
                    {ok, {<<Acc/binary, Name/binary>>, Rest@2}};

                false ->
                    {error, nil}
            end;

        _ ->
            {error, nil}
    end.

-file("src/swatch.gleam", 571).
-spec important_match(list({swatch@internal@lexer:lex_token(), boolean()})) -> {ok,
        {binary(), list({swatch@internal@lexer:lex_token(), boolean()})}} |
    {error, nil}.
important_match(Tokens) ->
    important_match_loop(Tokens, <<"!"/utf8>>).

-file("src/swatch.gleam", 480).
-spec leading_name(list({swatch@internal@lexer:lex_token(), boolean()})) -> {ok,
        {binary(), list({swatch@internal@lexer:lex_token(), boolean()})}} |
    {error, nil}.
leading_name(Tokens) ->
    case Tokens of
        [{{lex_ident, Name}, _} | Rest] ->
            {ok, {Name, Rest}};

        [{{lex_function, Name@1}, _} | Rest@1] ->
            {ok, {Name@1, Rest@1}};

        _ ->
            {error, nil}
    end.

-file("src/swatch.gleam", 231).
-spec head_flag(list({swatch@internal@lexer:lex_token(), boolean()})) -> boolean().
head_flag(Tokens) ->
    case Tokens of
        [{_, Flag} | _] ->
            Flag;

        [] ->
            false
    end.

-file("src/swatch.gleam", 442).
-spec promote_if_nested(state(), boolean()) -> state().
promote_if_nested(State, Nested) ->
    case Nested of
        true ->
            {state,
                selector_mode,
                erlang:element(3, State),
                erlang:element(4, State),
                erlang:element(5, State)};

        false ->
            State
    end.

-file("src/swatch.gleam", 490).
-spec classify_delim(
    binary(),
    list({swatch@internal@lexer:lex_token(), boolean()}),
    state(),
    list(token())
) -> {list({swatch@internal@lexer:lex_token(), boolean()}),
    state(),
    list(token())}.
classify_delim(S, Rest, State, Out) ->
    case S of
        <<"&"/utf8>> ->
            State2 = case erlang:element(2, State) of
                property_mode ->
                    {state,
                        selector_mode,
                        erlang:element(3, State),
                        erlang:element(4, State),
                        erlang:element(5, State)};

                value_mode ->
                    {state,
                        selector_mode,
                        erlang:element(3, State),
                        erlang:element(4, State),
                        erlang:element(5, State)};

                _ ->
                    State
            end,
            {Rest, State2, [{selector, <<"&"/utf8>>} | Out]};

        <<"*"/utf8>> ->
            State@1 = case erlang:element(2, State) of
                property_mode ->
                    promote_if_nested(State, head_flag(Rest));

                _ ->
                    State
            end,
            case erlang:element(2, State@1) of
                selector_mode ->
                    {Rest, State@1, [{selector, <<"*"/utf8>>} | Out]};

                _ ->
                    {Rest, State@1, [{operator, <<"*"/utf8>>} | Out]}
            end;

        <<"."/utf8>> ->
            State@2 = case erlang:element(2, State) of
                property_mode ->
                    promote_if_nested(State, head_flag(Rest));

                _ ->
                    State
            end,
            case erlang:element(2, State@2) of
                selector_mode ->
                    case leading_name(Rest) of
                        {ok, {Name, Rest2}} ->
                            {Rest2,
                                State@2,
                                [{class_selector, <<"."/utf8, Name/binary>>} |
                                    Out]};

                        {error, _} ->
                            {Rest, State@2, [{punctuation, <<"."/utf8>>} | Out]}
                    end;

                _ ->
                    {Rest, State@2, [{punctuation, <<"."/utf8>>} | Out]}
            end;

        <<"<"/utf8>> ->
            case Rest of
                [{{lex_delim, <<"="/utf8>>}, _} | Rest2@1] ->
                    {Rest2@1, State, [{operator, <<"<="/utf8>>} | Out]};

                _ ->
                    {Rest, State, [{operator, <<"<"/utf8>>} | Out]}
            end;

        <<">"/utf8>> ->
            case Rest of
                [{{lex_delim, <<"="/utf8>>}, _} | Rest2@2] ->
                    {Rest2@2, State, [{operator, <<">="/utf8>>} | Out]};

                _ ->
                    {Rest, State, [{operator, <<">"/utf8>>} | Out]}
            end;

        <<"|"/utf8>> ->
            case Rest of
                [{{lex_delim, <<"|"/utf8>>}, _} | Rest2@3] ->
                    {Rest2@3, State, [{operator, <<"||"/utf8>>} | Out]};

                _ ->
                    {Rest, State, [{operator, <<"|"/utf8>>} | Out]}
            end;

        <<"!"/utf8>> ->
            case important_match(Rest) of
                {ok, {Text, Rest2@4}} ->
                    {Rest2@4, State, [{important, Text} | Out]};

                {error, _} ->
                    {Rest, State, [{operator, <<"!"/utf8>>} | Out]}
            end;

        <<"/"/utf8>> ->
            {Rest, State, [{operator, S} | Out]};

        <<"~"/utf8>> ->
            {Rest, State, [{operator, S} | Out]};

        <<"="/utf8>> ->
            {Rest, State, [{operator, S} | Out]};

        <<"+"/utf8>> ->
            {Rest, State, [{operator, S} | Out]};

        <<"-"/utf8>> ->
            {Rest, State, [{operator, S} | Out]};

        _ ->
            {Rest, State, [{other, S} | Out]}
    end.

-file("src/swatch.gleam", 677).
-spec attribute_delim_head(
    binary(),
    list({swatch@internal@lexer:lex_token(), boolean()}),
    attribute_position()
) -> attribute_head().
attribute_delim_head(D, Rest, Position) ->
    case D of
        <<"="/utf8>> ->
            {head_emit, [{operator, <<"="/utf8>>}], Rest, after_matcher};

        <<"~"/utf8>> ->
            case Rest of
                [{{lex_delim, <<"="/utf8>>}, _} | Rest2] ->
                    {head_emit,
                        [{operator, <<D/binary, "="/utf8>>}],
                        Rest2,
                        after_matcher};

                _ ->
                    head_other
            end;

        <<"^"/utf8>> ->
            case Rest of
                [{{lex_delim, <<"="/utf8>>}, _} | Rest2] ->
                    {head_emit,
                        [{operator, <<D/binary, "="/utf8>>}],
                        Rest2,
                        after_matcher};

                _ ->
                    head_other
            end;

        <<"$"/utf8>> ->
            case Rest of
                [{{lex_delim, <<"="/utf8>>}, _} | Rest2] ->
                    {head_emit,
                        [{operator, <<D/binary, "="/utf8>>}],
                        Rest2,
                        after_matcher};

                _ ->
                    head_other
            end;

        <<"|"/utf8>> ->
            case Rest of
                [{{lex_delim, <<"="/utf8>>}, _} | Rest2@1] ->
                    {head_emit,
                        [{operator, <<"|="/utf8>>}],
                        Rest2@1,
                        after_matcher};

                _ ->
                    {head_emit, [{operator, <<"|"/utf8>>}], Rest, Position}
            end;

        <<"*"/utf8>> ->
            case Rest of
                [{{lex_delim, <<"="/utf8>>}, _} | Rest2@2] ->
                    {head_emit,
                        [{operator, <<"*="/utf8>>}],
                        Rest2@2,
                        after_matcher};

                [{{lex_delim, <<"|"/utf8>>}, _} | Rest2@3] ->
                    case Position of
                        before_matcher ->
                            {head_emit,
                                [{selector, <<"*"/utf8>>},
                                    {operator, <<"|"/utf8>>}],
                                Rest2@3,
                                Position};

                        _ ->
                            head_other
                    end;

                _ ->
                    head_other
            end;

        _ ->
            head_other
    end.

-file("src/swatch.gleam", 839).
-spec emit_attribute_identifier(binary(), attribute_position()) -> {token(),
    attribute_position()}.
emit_attribute_identifier(Name, Position) ->
    case Position of
        before_matcher ->
            {{attribute_name, Name}, before_matcher};

        after_matcher ->
            {{attribute_value, Name}, after_value};

        after_value ->
            {{attribute_flag, Name}, after_flag};

        after_flag ->
            {{other, Name}, after_flag}
    end.

-file("src/swatch.gleam", 853).
-spec advance_past_attribute_value(attribute_position()) -> attribute_position().
advance_past_attribute_value(Position) ->
    case Position of
        after_matcher ->
            after_value;

        _ ->
            Position
    end.

-file("src/swatch.gleam", 649).
-spec attribute_head(
    swatch@internal@lexer:lex_token(),
    list({swatch@internal@lexer:lex_token(), boolean()}),
    attribute_position()
) -> attribute_head().
attribute_head(Token, Rest, Position) ->
    case Token of
        lex_close_bracket ->
            {head_close, Rest};

        {lex_whitespace, S} ->
            {head_emit, [{whitespace, S}], Rest, Position};

        {lex_comment, S@1} ->
            {head_emit, [{comment, S@1}], Rest, Position};

        {lex_string, S@2} ->
            {head_emit,
                [{string, S@2}],
                Rest,
                advance_past_attribute_value(Position)};

        {lex_ident, Name} ->
            {Emitted, Position2} = emit_attribute_identifier(Name, Position),
            {head_emit, [Emitted], Rest, Position2};

        {lex_function, Name@1} ->
            {Emitted@1, Position2@1} = emit_attribute_identifier(
                Name@1,
                Position
            ),
            {head_emit, [Emitted@1], Rest, Position2@1};

        {lex_delim, D} ->
            attribute_delim_head(D, Rest, Position);

        _ ->
            head_other
    end.

-file("src/swatch.gleam", 714).
-spec take_attribute_other_token_run(
    list({swatch@internal@lexer:lex_token(), boolean()}),
    attribute_position(),
    binary()
) -> {binary(), list({swatch@internal@lexer:lex_token(), boolean()})}.
take_attribute_other_token_run(Tokens, Position, Acc) ->
    case Tokens of
        [] ->
            {Acc, []};

        [{Token, _} | Rest] ->
            case attribute_head(Token, Rest, Position) of
                head_other ->
                    take_attribute_other_token_run(
                        Rest,
                        Position,
                        <<Acc/binary,
                            (swatch@internal@lexer:lex_token_to_source(Token))/binary>>
                    );

                _ ->
                    {Acc, Tokens}
            end
    end.

-file("src/swatch.gleam", 609).
-spec classify_attribute_body_loop(
    list({swatch@internal@lexer:lex_token(), boolean()}),
    attribute_position(),
    list(token())
) -> {list({swatch@internal@lexer:lex_token(), boolean()}), list(token())}.
classify_attribute_body_loop(Tokens, Position, Out) ->
    case Tokens of
        [] ->
            {[], Out};

        [{Token, _} | Rest] ->
            case attribute_head(Token, Rest, Position) of
                {head_close, Remaining} ->
                    {Remaining, [{punctuation, <<"]"/utf8>>} | Out]};

                {head_emit, Emitted, Remaining@1, Position2} ->
                    classify_attribute_body_loop(
                        Remaining@1,
                        Position2,
                        gleam@list:fold(
                            Emitted,
                            Out,
                            fun(Acc, T) -> [T | Acc] end
                        )
                    );

                head_other ->
                    {Text, Remaining@2} = take_attribute_other_token_run(
                        Tokens,
                        Position,
                        <<""/utf8>>
                    ),
                    classify_attribute_body_loop(
                        Remaining@2,
                        Position,
                        [{other, Text} | Out]
                    )
            end
    end.

-file("src/swatch.gleam", 596).
-spec classify_attribute_selector(
    list({swatch@internal@lexer:lex_token(), boolean()}),
    state(),
    list(token())
) -> {list({swatch@internal@lexer:lex_token(), boolean()}),
    state(),
    list(token())}.
classify_attribute_selector(Rest, State, Out) ->
    {Remaining, Out2} = classify_attribute_body_loop(
        Rest,
        before_matcher,
        [{punctuation, <<"["/utf8>>} | Out]
    ),
    {Remaining, State, Out2}.

-file("src/swatch.gleam", 766).
-spec at_prelude_grouping_level(binary(), list(parenthesis())) -> boolean().
at_prelude_grouping_level(At_rule, Stack) ->
    (At_rule /= <<""/utf8>>) andalso case Stack of
        [] ->
            true;

        [grouping | _] ->
            true;

        _ ->
            false
    end.

-file("src/swatch.gleam", 790).
-spec at_rule_context(binary()) -> at_rule_context().
at_rule_context(At_rule) ->
    case string:lowercase(At_rule) of
        <<"@scope"/utf8>> ->
            {at_rule_context, selector_mode, []};

        <<"@supports"/utf8>> ->
            {at_rule_context,
                property_mode,
                [{<<"selector"/utf8>>, selector_mode}]};

        <<"@container"/utf8>> ->
            {at_rule_context,
                property_mode,
                [{<<"style"/utf8>>, property_mode},
                    {<<"scroll-state"/utf8>>, property_mode}]};

        <<"@import"/utf8>> ->
            {at_rule_context,
                property_mode,
                [{<<"supports"/utf8>>, property_mode}]};

        _ ->
            {at_rule_context, property_mode, []}
    end.

-file("src/swatch.gleam", 825).
-spec prelude_parenthesis_mode(binary()) -> mode().
prelude_parenthesis_mode(At_rule) ->
    erlang:element(2, at_rule_context(At_rule)).

-file("src/swatch.gleam", 814).
-spec function_context_flip(binary(), binary(), mode()) -> mode().
function_context_flip(Fn_name, At_rule, Current_mode) ->
    case gleam@list:key_find(
        erlang:element(3, at_rule_context(At_rule)),
        Fn_name
    ) of
        {ok, Mode} ->
            Mode;

        {error, _} ->
            Current_mode
    end.

-file("src/swatch.gleam", 469).
-spec classify_pseudo(
    list({swatch@internal@lexer:lex_token(), boolean()}),
    state(),
    list(token())
) -> {list({swatch@internal@lexer:lex_token(), boolean()}),
    state(),
    list(token())}.
classify_pseudo(Rest, State, Out) ->
    case leading_name(Rest) of
        {ok, {Name, Rest2}} ->
            {Rest2, State, [{pseudo_selector, <<":"/utf8, Name/binary>>} | Out]};

        {error, _} ->
            {Rest, State, [{punctuation, <<":"/utf8>>} | Out]}
    end.

-file("src/swatch.gleam", 449).
-spec classify_colon(
    list({swatch@internal@lexer:lex_token(), boolean()}),
    state(),
    list(token())
) -> {list({swatch@internal@lexer:lex_token(), boolean()}),
    state(),
    list(token())}.
classify_colon(Rest, State, Out) ->
    State@1 = case erlang:element(2, State) of
        property_mode ->
            promote_if_nested(State, head_flag(Rest));

        _ ->
            State
    end,
    case {erlang:element(2, State@1), erlang:element(4, State@1)} of
        {property_mode, _} ->
            {Rest,
                {state,
                    value_mode,
                    erlang:element(3, State@1),
                    erlang:element(4, State@1),
                    erlang:element(5, State@1)},
                [{punctuation, <<":"/utf8>>} | Out]};

        {selector_mode, _} ->
            classify_pseudo(Rest, State@1, Out);

        {at_rule_mode, []} ->
            classify_pseudo(Rest, State@1, Out);

        {_, _} ->
            {Rest, State@1, [{punctuation, <<":"/utf8>>} | Out]}
    end.

-file("src/swatch.gleam", 256).
-spec classify_one(
    {swatch@internal@lexer:lex_token(), boolean()},
    list({swatch@internal@lexer:lex_token(), boolean()}),
    state(),
    list(token())
) -> {list({swatch@internal@lexer:lex_token(), boolean()}),
    state(),
    list(token())}.
classify_one(Atoken, Rest, State, Out) ->
    {Token, Flag} = Atoken,
    case Token of
        {lex_whitespace, S} ->
            {Rest, State, [{whitespace, S} | Out]};

        {lex_comment, S@1} ->
            {Rest, State, [{comment, S@1} | Out]};

        lex_cdo ->
            {Rest, State, [{comment, <<"<!--"/utf8>>} | Out]};

        lex_cdc ->
            {Rest, State, [{comment, <<"-->"/utf8>>} | Out]};

        {lex_string, S@2} ->
            {Rest, State, [{string, S@2} | Out]};

        {lex_url_body, S@3} ->
            {Rest, State, [{string, S@3} | Out]};

        {lex_number, N} ->
            {Rest, State, [{number, N} | Out]};

        {lex_percentage, N@1} ->
            {Rest, State, [{unit, <<"%"/utf8>>}, {number, N@1} | Out]};

        {lex_dimension, N@2, U} ->
            {Rest, State, [{unit, U}, {number, N@2} | Out]};

        {lex_urange, S@4} ->
            {Rest, State, [{keyword, S@4} | Out]};

        lex_comma ->
            {Rest, State, [{punctuation, <<","/utf8>>} | Out]};

        lex_close_bracket ->
            {Rest, State, [{punctuation, <<"]"/utf8>>} | Out]};

        lex_open_brace ->
            State2 = {state,
                property_mode,
                [property_mode | erlang:element(3, State)],
                erlang:element(4, State),
                <<""/utf8>>},
            {Rest, State2, [{punctuation, <<"{"/utf8>>} | Out]};

        lex_close_brace ->
            Stack2 = case erlang:element(3, State) of
                [] ->
                    [];

                [_ | Tail] ->
                    Tail
            end,
            Outer_mode = case Stack2 of
                [] ->
                    selector_mode;

                [Mode | _] ->
                    Mode
            end,
            State2@1 = {state,
                Outer_mode,
                Stack2,
                erlang:element(4, State),
                <<""/utf8>>},
            {Rest, State2@1, [{punctuation, <<"}"/utf8>>} | Out]};

        lex_semicolon ->
            New_mode = case erlang:element(2, State) of
                value_mode ->
                    property_mode;

                at_rule_mode ->
                    case erlang:element(3, State) of
                        [Mode@1 | _] ->
                            Mode@1;

                        [] ->
                            selector_mode
                    end;

                Other ->
                    Other
            end,
            State2@2 = {state,
                New_mode,
                erlang:element(3, State),
                erlang:element(4, State),
                <<""/utf8>>},
            {Rest, State2@2, [{punctuation, <<";"/utf8>>} | Out]};

        lex_colon ->
            case Rest of
                [{lex_colon, _} | Rest2] ->
                    case leading_name(Rest2) of
                        {ok, {Name, Rest3}} ->
                            {Rest3,
                                State,
                                [{pseudo_selector, <<"::"/utf8, Name/binary>>} |
                                    Out]};

                        {error, _} ->
                            {Rest2, State, [{other, <<"::"/utf8>>} | Out]}
                    end;

                _ ->
                    classify_colon(Rest, State, Out)
            end;

        {lex_at_keyword, S@5} ->
            State2@3 = {state,
                at_rule_mode,
                erlang:element(3, State),
                erlang:element(4, State),
                S@5},
            {Rest, State2@3, [{at_rule, S@5} | Out]};

        {lex_hash, S@6} ->
            State@1 = case erlang:element(2, State) of
                property_mode ->
                    promote_if_nested(State, head_flag(Rest));

                _ ->
                    State
            end,
            Name@1 = gleam@string:drop_start(S@6, 1),
            Token@1 = case erlang:element(2, State@1) of
                value_mode ->
                    case swatch@internal@lexer:is_valid_hex_color(Name@1) of
                        true ->
                            {hex_color, S@6};

                        false ->
                            {other, S@6}
                    end;

                selector_mode ->
                    {id_selector, S@6};

                _ ->
                    {other, S@6}
            end,
            {Rest, State@1, [Token@1 | Out]};

        {lex_ident, Name@2} ->
            case gleam_stdlib:string_starts_with(Name@2, <<"--"/utf8>>) of
                true ->
                    {Rest, State, [{variable, Name@2} | Out]};

                false ->
                    case erlang:element(2, State) of
                        selector_mode ->
                            {Rest, State, [{selector, Name@2} | Out]};

                        value_mode ->
                            {Rest, State, [{keyword, Name@2} | Out]};

                        at_rule_mode ->
                            {Rest, State, [{keyword, Name@2} | Out]};

                        property_mode ->
                            case head_flag(Rest) of
                                true ->
                                    {Rest,
                                        {state,
                                            selector_mode,
                                            erlang:element(3, State),
                                            erlang:element(4, State),
                                            erlang:element(5, State)},
                                        [{selector, Name@2} | Out]};

                                false ->
                                    {Rest, State, [{property, Name@2} | Out]}
                            end
                    end
            end;

        {lex_function, Name@3} ->
            case Rest of
                [{lex_open_paren, _} | After] ->
                    New_mode@1 = function_context_flip(
                        string:lowercase(Name@3),
                        string:lowercase(erlang:element(5, State)),
                        erlang:element(2, State)
                    ),
                    State2@4 = {state,
                        New_mode@1,
                        erlang:element(3, State),
                        [{function_call, erlang:element(2, State)} |
                            erlang:element(4, State)],
                        erlang:element(5, State)},
                    {After,
                        State2@4,
                        [{punctuation, <<"("/utf8>>}, {function, Name@3} | Out]};

                _ ->
                    {Rest, State, [{function, Name@3} | Out]}
            end;

        lex_open_paren ->
            New_mode@2 = case at_prelude_grouping_level(
                erlang:element(5, State),
                erlang:element(4, State)
            ) of
                true ->
                    prelude_parenthesis_mode(erlang:element(5, State));

                false ->
                    erlang:element(2, State)
            end,
            State2@5 = {state,
                New_mode@2,
                erlang:element(3, State),
                [grouping | erlang:element(4, State)],
                erlang:element(5, State)},
            {Rest, State2@5, [{punctuation, <<"("/utf8>>} | Out]};

        lex_close_paren ->
            {Popped, Rest_stack} = case erlang:element(4, State) of
                [Head | Tail@1] ->
                    {{ok, Head}, Tail@1};

                [] ->
                    {{error, nil}, []}
            end,
            New_mode@3 = case Popped of
                {ok, {function_call, Restore_mode}} ->
                    Restore_mode;

                {ok, grouping} ->
                    case at_prelude_grouping_level(
                        erlang:element(5, State),
                        Rest_stack
                    ) of
                        true ->
                            at_rule_mode;

                        false ->
                            erlang:element(2, State)
                    end;

                {error, _} ->
                    erlang:element(2, State)
            end,
            State2@6 = {state,
                New_mode@3,
                erlang:element(3, State),
                Rest_stack,
                erlang:element(5, State)},
            {Rest, State2@6, [{punctuation, <<")"/utf8>>} | Out]};

        lex_open_bracket ->
            State@2 = case erlang:element(2, State) of
                property_mode ->
                    promote_if_nested(State, Flag);

                _ ->
                    State
            end,
            case erlang:element(2, State@2) of
                selector_mode ->
                    classify_attribute_selector(Rest, State@2, Out);

                _ ->
                    {Rest, State@2, [{punctuation, <<"["/utf8>>} | Out]}
            end;

        {lex_delim, S@7} ->
            classify_delim(S@7, Rest, State, Out)
    end.

-file("src/swatch.gleam", 242).
-spec classify_loop(
    list({swatch@internal@lexer:lex_token(), boolean()}),
    state(),
    list(token())
) -> list(token()).
classify_loop(Tokens, State, Out) ->
    case Tokens of
        [] ->
            lists:reverse(Out);

        [Token | Rest] ->
            {Rest2, State2, Out2} = classify_one(Token, Rest, State, Out),
            classify_loop(Rest2, State2, Out2)
    end.

-file("src/swatch.gleam", 238).
-spec classify(list(swatch@internal@lexer:lex_token())) -> list(token()).
classify(Tokens) ->
    classify_loop(
        gleam@list:zip(Tokens, swatch@internal@lexer:annotate_nested(Tokens)),
        initial_state(),
        []
    ).

-file("src/swatch.gleam", 79).
?DOC(
    " Tokenize some CSS source. The returned tokens, concatenated, will\n"
    " reproduce the original source.\n"
).
-spec to_tokens(binary()) -> list(token()).
to_tokens(Code) ->
    classify(swatch@internal@lexer:lex(Code)).

-file("src/swatch.gleam", 86).
?DOC(
    " The verbatim source text a token was cut from, including any sigil or\n"
    " delimiters (`.` for a class, the quotes of a string, `/* */` for a\n"
    " comment).\n"
).
-spec token_to_source(token()) -> binary().
token_to_source(Token) ->
    case Token of
        {whitespace, S} ->
            S;

        {comment, S@1} ->
            S@1;

        {selector, S@2} ->
            S@2;

        {class_selector, S@3} ->
            S@3;

        {id_selector, S@4} ->
            S@4;

        {pseudo_selector, S@5} ->
            S@5;

        {attribute_name, S@6} ->
            S@6;

        {attribute_value, S@7} ->
            S@7;

        {attribute_flag, S@8} ->
            S@8;

        {at_rule, S@9} ->
            S@9;

        {property, S@10} ->
            S@10;

        {variable, S@11} ->
            S@11;

        {string, S@12} ->
            S@12;

        {number, S@13} ->
            S@13;

        {unit, S@14} ->
            S@14;

        {hex_color, S@15} ->
            S@15;

        {function, S@16} ->
            S@16;

        {keyword, S@17} ->
            S@17;

        {important, S@18} ->
            S@18;

        {operator, S@19} ->
            S@19;

        {punctuation, S@20} ->
            S@20;

        {other, S@21} ->
            S@21
    end.

-file("src/swatch.gleam", 116).
?DOC(
    " Concatenate a token list back into source text — the inverse of\n"
    " [`to_tokens`](#to_tokens). `to_source(to_tokens(css)) == css` holds for\n"
    " any input.\n"
).
-spec to_source(list(token())) -> binary().
to_source(Tokens) ->
    _pipe = Tokens,
    _pipe@1 = gleam@list:map(_pipe, fun token_to_source/1),
    erlang:list_to_binary(_pipe@1).

-file("src/swatch.gleam", 891).
-spec wrap(binary(), binary()) -> binary().
wrap(Class, Content) ->
    <<<<<<<<"<span class=\""/utf8, Class/binary>>/binary, "\">"/utf8>>/binary,
            (houdini:escape(Content))/binary>>/binary,
        "</span>"/utf8>>.

-file("src/swatch.gleam", 864).
-spec token_to_html(token()) -> binary().
token_to_html(Token) ->
    case Token of
        {whitespace, S} ->
            S;

        {comment, S@1} ->
            wrap(<<"hl-comment"/utf8>>, S@1);

        {selector, S@2} ->
            wrap(<<"hl-selector"/utf8>>, S@2);

        {class_selector, S@3} ->
            wrap(<<"hl-class"/utf8>>, S@3);

        {id_selector, S@4} ->
            wrap(<<"hl-id"/utf8>>, S@4);

        {pseudo_selector, S@5} ->
            wrap(<<"hl-pseudo"/utf8>>, S@5);

        {attribute_name, S@6} ->
            wrap(<<"hl-attribute"/utf8>>, S@6);

        {attribute_value, S@7} ->
            wrap(<<"hl-attribute-value"/utf8>>, S@7);

        {attribute_flag, S@8} ->
            wrap(<<"hl-attribute-flag"/utf8>>, S@8);

        {at_rule, S@9} ->
            wrap(<<"hl-at-rule"/utf8>>, S@9);

        {property, S@10} ->
            wrap(<<"hl-property"/utf8>>, S@10);

        {variable, S@11} ->
            wrap(<<"hl-variable"/utf8>>, S@11);

        {string, S@12} ->
            wrap(<<"hl-string"/utf8>>, S@12);

        {number, S@13} ->
            wrap(<<"hl-number"/utf8>>, S@13);

        {unit, S@14} ->
            wrap(<<"hl-unit"/utf8>>, S@14);

        {hex_color, S@15} ->
            wrap(<<"hl-hex"/utf8>>, S@15);

        {function, S@16} ->
            wrap(<<"hl-function"/utf8>>, S@16);

        {keyword, S@17} ->
            wrap(<<"hl-keyword"/utf8>>, S@17);

        {important, S@18} ->
            wrap(<<"hl-important"/utf8>>, S@18);

        {operator, S@19} ->
            wrap(<<"hl-operator"/utf8>>, S@19);

        {punctuation, S@20} ->
            wrap(<<"hl-punctuation"/utf8>>, S@20);

        {other, S@21} ->
            wrap(<<"hl-other"/utf8>>, S@21)
    end.

-file("src/swatch.gleam", 184).
?DOC(
    " Render an already-tokenized list as HTML. Like [`to_html`](#to_html) but\n"
    " skips re-tokenizing, for callers that already hold the token list.\n"
).
-spec tokens_to_html(list(token())) -> binary().
tokens_to_html(Tokens) ->
    _pipe = Tokens,
    _pipe@1 = gleam@list:map(_pipe, fun token_to_html/1),
    erlang:list_to_binary(_pipe@1).

-file("src/swatch.gleam", 176).
?DOC(
    " Render CSS source as HTML. Each token is wrapped in a `<span>` with a\n"
    " CSS class describing its kind. Wrap the result in\n"
    " `<pre><code>...</code></pre>` and style the classes below.\n"
    "\n"
    " | Token          | CSS class          |\n"
    " | -------------- | ------------------ |\n"
    " | Whitespace     | (no wrapper)       |\n"
    " | Comment        | hl-comment         |\n"
    " | Selector       | hl-selector        |\n"
    " | ClassSelector  | hl-class           |\n"
    " | IdSelector     | hl-id              |\n"
    " | PseudoSelector | hl-pseudo          |\n"
    " | AttributeName  | hl-attribute       |\n"
    " | AttributeValue | hl-attribute-value |\n"
    " | AttributeFlag  | hl-attribute-flag  |\n"
    " | AtRule         | hl-at-rule         |\n"
    " | Property       | hl-property        |\n"
    " | Variable       | hl-variable        |\n"
    " | String         | hl-string          |\n"
    " | Number         | hl-number          |\n"
    " | Unit           | hl-unit            |\n"
    " | HexColor       | hl-hex             |\n"
    " | Function       | hl-function        |\n"
    " | Keyword        | hl-keyword         |\n"
    " | Important      | hl-important       |\n"
    " | Operator       | hl-operator        |\n"
    " | Punctuation    | hl-punctuation     |\n"
    " | Other          | hl-other           |\n"
    "\n"
    " Starter stylesheet:\n"
    "\n"
    " ```css\n"
    " pre code .hl-comment         { color: #6a737d; font-style: italic }\n"
    " pre code .hl-selector        { color: #d73a49 }\n"
    " pre code .hl-class           { color: #6f42c1 }\n"
    " pre code .hl-id              { color: #6f42c1 }\n"
    " pre code .hl-pseudo          { color: #6f42c1 }\n"
    " pre code .hl-attribute       { color: #6f42c1 }\n"
    " pre code .hl-attribute-value { color: #032f62 }\n"
    " pre code .hl-attribute-flag  { color: #6f42c1 }\n"
    " pre code .hl-at-rule         { color: #d73a49 }\n"
    " pre code .hl-property        { color: #005cc5 }\n"
    " pre code .hl-variable        { color: #e36209 }\n"
    " pre code .hl-string          { color: #032f62 }\n"
    " pre code .hl-number          { color: #005cc5 }\n"
    " pre code .hl-unit            { color: #005cc5 }\n"
    " pre code .hl-hex             { color: #005cc5 }\n"
    " pre code .hl-function        { color: #6f42c1 }\n"
    " pre code .hl-keyword         { color: #22863a }\n"
    " pre code .hl-important       { color: #d73a49; font-weight: bold }\n"
    " pre code .hl-operator        { color: #d73a49 }\n"
    " pre code .hl-punctuation     { color: #24292e }\n"
    " pre code .hl-other           { color: #24292e }\n"
    " ```\n"
).
-spec to_html(binary()) -> binary().
to_html(Code) ->
    _pipe = Code,
    _pipe@1 = to_tokens(_pipe),
    tokens_to_html(_pipe@1).

-file("src/swatch.gleam", 897).
-spec token_to_ansi(token()) -> binary().
token_to_ansi(Token) ->
    case Token of
        {whitespace, S} ->
            gleam_community@ansi:reset(S);

        {comment, S@1} ->
            gleam_community@ansi:italic(gleam_community@ansi:gray(S@1));

        {selector, S@2} ->
            gleam_community@ansi:yellow(S@2);

        {class_selector, S@3} ->
            gleam_community@ansi:yellow(S@3);

        {id_selector, S@4} ->
            gleam_community@ansi:yellow(S@4);

        {pseudo_selector, S@5} ->
            gleam_community@ansi:yellow(S@5);

        {attribute_name, S@6} ->
            gleam_community@ansi:yellow(S@6);

        {attribute_value, S@7} ->
            gleam_community@ansi:green(S@7);

        {attribute_flag, S@8} ->
            gleam_community@ansi:yellow(S@8);

        {at_rule, S@9} ->
            gleam_community@ansi:magenta(S@9);

        {property, S@10} ->
            gleam_community@ansi:cyan(S@10);

        {variable, S@11} ->
            gleam_community@ansi:cyan(S@11);

        {string, S@12} ->
            gleam_community@ansi:green(S@12);

        {number, S@13} ->
            gleam_community@ansi:green(S@13);

        {unit, S@14} ->
            gleam_community@ansi:green(S@14);

        {hex_color, S@15} ->
            gleam_community@ansi:green(S@15);

        {function, S@16} ->
            gleam_community@ansi:blue(S@16);

        {keyword, S@17} ->
            gleam_community@ansi:yellow(S@17);

        {important, S@18} ->
            gleam_community@ansi:bold(gleam_community@ansi:red(S@18));

        {operator, S@19} ->
            gleam_community@ansi:magenta(S@19);

        {punctuation, S@20} ->
            gleam_community@ansi:reset(S@20);

        {other, S@21} ->
            gleam_community@ansi:reset(S@21)
    end.

-file("src/swatch.gleam", 214).
?DOC(
    " Render an already-tokenized list for the terminal. Like\n"
    " [`to_ansi`](#to_ansi), but skips re-tokenizing for callers that already\n"
    " hold the token list.\n"
).
-spec tokens_to_ansi(list(token())) -> binary().
tokens_to_ansi(Tokens) ->
    _pipe = Tokens,
    _pipe@1 = gleam@list:map(_pipe, fun token_to_ansi/1),
    erlang:list_to_binary(_pipe@1).

-file("src/swatch.gleam", 205).
?DOC(
    " Render CSS source for the terminal using ANSI color escapes.\n"
    "\n"
    " | Token                                                                                         | Color       |\n"
    " | --------------------------------------------------------------------------------------------- | ----------- |\n"
    " | Selector, ClassSelector, IdSelector, PseudoSelector, AttributeName, AttributeFlag, Keyword    | yellow      |\n"
    " | Property, Variable                                                                            | cyan        |\n"
    " | String, Number, Unit, HexColor, AttributeValue                                                | green       |\n"
    " | Function                                                                                      | blue        |\n"
    " | AtRule, Operator                                                                              | magenta     |\n"
    " | Important                                                                                     | bold red    |\n"
    " | Comment                                                                                       | italic gray |\n"
    " | Whitespace, Punctuation, Other                                                                | reset       |\n"
    "\n"
    " Structural tokens use `ansi.reset` so an unclosed attribute from\n"
    " upstream text can't bleed into characters like `{` and `}`.\n"
).
-spec to_ansi(binary()) -> binary().
to_ansi(Code) ->
    _pipe = Code,
    _pipe@1 = to_tokens(_pipe),
    tokens_to_ansi(_pipe@1).