Packages

An Erlang lexer and syntax highlighter for Gleam!

Current section

Files

Jump to
pearl src pearl@highlight.erl
Raw

src/pearl@highlight.erl

-module(pearl@highlight).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/pearl/highlight.gleam").
-export([tokens/1, ansi/1, html/1]).
-export_type([token/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.
-type token() :: {whitespace, binary()} |
{keyword, binary()} |
{variable, binary()} |
{string, binary()} |
{atom, binary()} |
{number, binary()} |
{module, binary()} |
{function, binary()} |
{operator, binary()} |
{comment, binary()} |
{punctuation, binary()} |
{other, binary()}.
-file("src/pearl/highlight.gleam", 142).
-spec do_to_tokens(list(pearl@token:token()), list(token())) -> list(token()).
do_to_tokens(In, Out) ->
case In of
[] ->
lists:reverse(Out);
[{atom, Value, false}, left_paren | In@1] ->
do_to_tokens(
In@1,
[{punctuation, <<"("/utf8>>}, {function, Value} | Out]
);
[{atom, Function, false}, slash, {integer, Arity} | In@2] ->
do_to_tokens(
In@2,
[{number, Arity},
{punctuation, <<"/"/utf8>>},
{function, Function} |
Out]
);
[{atom, Module, false},
colon,
{atom, Function@1, false},
slash,
{integer, Arity@1} |
In@3] ->
do_to_tokens(
In@3,
[{number, Arity@1},
{punctuation, <<"/"/utf8>>},
{function, Function@1},
{punctuation, <<":"/utf8>>},
{module, Module} |
Out]
);
[{atom, Module@1, false}, colon, {atom, Function@2, false} | In@4] ->
do_to_tokens(
In@4,
[{function, Function@2},
{punctuation, <<":"/utf8>>},
{module, Module@1} |
Out]
);
[question, {variable, Macro_name} | In@5] ->
do_to_tokens(
In@5,
[{function, Macro_name}, {punctuation, <<"?"/utf8>>} | Out]
);
[{whitespace, Space} | In@6] ->
do_to_tokens(In@6, [{whitespace, Space} | Out]);
[{comment, Contents} | In@7] ->
do_to_tokens(In@7, [{comment, <<"%"/utf8, Contents/binary>>} | Out]);
[{doc_comment, Contents@1} | In@8] ->
do_to_tokens(
In@8,
[{comment, <<"%%"/utf8, Contents@1/binary>>} | Out]
);
[{module_comment, Contents@2} | In@9] ->
do_to_tokens(
In@9,
[{comment, <<"%%%"/utf8, Contents@2/binary>>} | Out]
);
[end_of_file | In@10] ->
do_to_tokens(In@10, Out);
[{character, Char} | In@11] ->
do_to_tokens(In@11, [{string, <<"$"/utf8, Char/binary>>} | Out]);
[{integer, Int} | In@12] ->
do_to_tokens(In@12, [{number, Int} | Out]);
[{float, Float} | In@13] ->
do_to_tokens(In@13, [{number, Float} | Out]);
[{atom, Name, true} | In@14] ->
do_to_tokens(
In@14,
[{atom, <<<<"'"/utf8, Name/binary>>/binary, "'"/utf8>>} | Out]
);
[{atom, Name@1, false} | In@15] ->
do_to_tokens(In@15, [{atom, Name@1} | Out]);
[{string, Contents@3} | In@16] ->
do_to_tokens(
In@16,
[{string,
<<<<"\""/utf8, Contents@3/binary>>/binary, "\""/utf8>>} |
Out]
);
[{triple_quoted_string,
Sigil,
Number_of_quotes,
Beginning_whitespace,
Lines,
End_indentation} |
In@17] ->
do_to_tokens(In@17, [{string, <<<<<<<<<<<<(case Sigil of
none ->
<<""/utf8>>;
{some, Sigil@1} ->
<<"~"/utf8,
Sigil@1/binary>>
end)/binary, (gleam@string:repeat(
<<"\""/utf8>>,
Number_of_quotes
))/binary>>/binary, Beginning_whitespace/binary>>/binary, (gleam@string:join(
gleam@list:map(
Lines,
fun(Line) ->
<<End_indentation/binary,
Line/binary>>
end
),
<<"\n"/utf8>>
))/binary>>/binary, "\n"/utf8>>/binary, End_indentation/binary>>/binary, (gleam@string:repeat(
<<"\""/utf8>>,
Number_of_quotes
))/binary>>} | Out]);
[{sigil, Sigil@2, Delimiter, Contents@4} | In@18] ->
do_to_tokens(
In@18,
[{string,
begin
{Opening, Closing} = pearl@token:sigil_delimiters(
Delimiter
),
<<<<<<<<"~"/utf8, Sigil@2/binary>>/binary,
Opening/binary>>/binary,
Contents@4/binary>>/binary,
Closing/binary>>
end} |
Out]
);
[{variable, Name@2} | In@19] ->
do_to_tokens(In@19, [{variable, Name@2} | Out]);
['after' | In@20] ->
do_to_tokens(In@20, [{keyword, <<"after"/utf8>>} | Out]);
['begin' | In@21] ->
do_to_tokens(In@21, [{keyword, <<"begin"/utf8>>} | Out]);
['case' | In@22] ->
do_to_tokens(In@22, [{keyword, <<"case"/utf8>>} | Out]);
['catch' | In@23] ->
do_to_tokens(In@23, [{keyword, <<"catch"/utf8>>} | Out]);
['cond' | In@24] ->
do_to_tokens(In@24, [{keyword, <<"cond"/utf8>>} | Out]);
['else' | In@25] ->
do_to_tokens(In@25, [{keyword, <<"else"/utf8>>} | Out]);
['end' | In@26] ->
do_to_tokens(In@26, [{keyword, <<"end"/utf8>>} | Out]);
['fun' | In@27] ->
do_to_tokens(In@27, [{keyword, <<"fun"/utf8>>} | Out]);
['if' | In@28] ->
do_to_tokens(In@28, [{keyword, <<"if"/utf8>>} | Out]);
['let' | In@29] ->
do_to_tokens(In@29, [{keyword, <<"let"/utf8>>} | Out]);
['maybe' | In@30] ->
do_to_tokens(In@30, [{keyword, <<"maybe"/utf8>>} | Out]);
['of' | In@31] ->
do_to_tokens(In@31, [{keyword, <<"of"/utf8>>} | Out]);
['receive' | In@32] ->
do_to_tokens(In@32, [{keyword, <<"receive"/utf8>>} | Out]);
['try' | In@33] ->
do_to_tokens(In@33, [{keyword, <<"try"/utf8>>} | Out]);
['when' | In@34] ->
do_to_tokens(In@34, [{keyword, <<"when"/utf8>>} | Out]);
[left_paren | In@35] ->
do_to_tokens(In@35, [{punctuation, <<"("/utf8>>} | Out]);
[right_paren | In@36] ->
do_to_tokens(In@36, [{punctuation, <<")"/utf8>>} | Out]);
[left_brace | In@37] ->
do_to_tokens(In@37, [{punctuation, <<"{"/utf8>>} | Out]);
[right_brace | In@38] ->
do_to_tokens(In@38, [{punctuation, <<"}"/utf8>>} | Out]);
[left_square | In@39] ->
do_to_tokens(In@39, [{punctuation, <<"["/utf8>>} | Out]);
[right_square | In@40] ->
do_to_tokens(In@40, [{punctuation, <<"]"/utf8>>} | Out]);
[comma | In@41] ->
do_to_tokens(In@41, [{punctuation, <<","/utf8>>} | Out]);
[semicolon | In@42] ->
do_to_tokens(In@42, [{punctuation, <<";"/utf8>>} | Out]);
[colon | In@43] ->
do_to_tokens(In@43, [{punctuation, <<":"/utf8>>} | Out]);
[dot | In@44] ->
do_to_tokens(In@44, [{punctuation, <<"."/utf8>>} | Out]);
[minus_greater | In@45] ->
do_to_tokens(In@45, [{punctuation, <<"->"/utf8>>} | Out]);
[double_less | In@46] ->
do_to_tokens(In@46, [{punctuation, <<"<<"/utf8>>} | Out]);
[double_greater | In@47] ->
do_to_tokens(In@47, [{punctuation, <<">>"/utf8>>} | Out]);
[hash | In@48] ->
do_to_tokens(In@48, [{punctuation, <<"#"/utf8>>} | Out]);
[double_colon | In@49] ->
do_to_tokens(In@49, [{punctuation, <<"::"/utf8>>} | Out]);
[double_dot | In@50] ->
do_to_tokens(In@50, [{punctuation, <<".."/utf8>>} | Out]);
[triple_dot | In@51] ->
do_to_tokens(In@51, [{punctuation, <<"..."/utf8>>} | Out]);
[question | In@52] ->
do_to_tokens(In@52, [{punctuation, <<"?"/utf8>>} | Out]);
[double_pipe | In@53] ->
do_to_tokens(In@53, [{operator, <<"||"/utf8>>} | Out]);
[equal_greater | In@54] ->
do_to_tokens(In@54, [{operator, <<"=>"/utf8>>} | Out]);
[colon_equal | In@55] ->
do_to_tokens(In@55, [{operator, <<":="/utf8>>} | Out]);
[less_minus | In@56] ->
do_to_tokens(In@56, [{operator, <<"<-"/utf8>>} | Out]);
[less_equal | In@57] ->
do_to_tokens(In@57, [{operator, <<"<="/utf8>>} | Out]);
[pipe | In@58] ->
do_to_tokens(In@58, [{operator, <<"|"/utf8>>} | Out]);
[double_equal | In@59] ->
do_to_tokens(In@59, [{operator, <<"=="/utf8>>} | Out]);
[slash_equal | In@60] ->
do_to_tokens(In@60, [{operator, <<"/="/utf8>>} | Out]);
[equal_less | In@61] ->
do_to_tokens(In@61, [{operator, <<"=<"/utf8>>} | Out]);
[less | In@62] ->
do_to_tokens(In@62, [{operator, <<"<"/utf8>>} | Out]);
[greater_equal | In@63] ->
do_to_tokens(In@63, [{operator, <<">="/utf8>>} | Out]);
[greater | In@64] ->
do_to_tokens(In@64, [{operator, <<">"/utf8>>} | Out]);
[equal_colon_equal | In@65] ->
do_to_tokens(In@65, [{operator, <<"=:="/utf8>>} | Out]);
[equal_slash_equal | In@66] ->
do_to_tokens(In@66, [{operator, <<"=/="/utf8>>} | Out]);
[plus | In@67] ->
do_to_tokens(In@67, [{operator, <<"+"/utf8>>} | Out]);
[minus | In@68] ->
do_to_tokens(In@68, [{operator, <<"-"/utf8>>} | Out]);
[star | In@69] ->
do_to_tokens(In@69, [{operator, <<"*"/utf8>>} | Out]);
[slash | In@70] ->
do_to_tokens(In@70, [{operator, <<"/"/utf8>>} | Out]);
['bnot' | In@71] ->
do_to_tokens(In@71, [{operator, <<"bnot"/utf8>>} | Out]);
['div' | In@72] ->
do_to_tokens(In@72, [{operator, <<"div"/utf8>>} | Out]);
['rem' | In@73] ->
do_to_tokens(In@73, [{operator, <<"rem"/utf8>>} | Out]);
['band' | In@74] ->
do_to_tokens(In@74, [{operator, <<"band"/utf8>>} | Out]);
['bor' | In@75] ->
do_to_tokens(In@75, [{operator, <<"bor"/utf8>>} | Out]);
['bxor' | In@76] ->
do_to_tokens(In@76, [{operator, <<"bxor"/utf8>>} | Out]);
['bsl' | In@77] ->
do_to_tokens(In@77, [{operator, <<"bsl"/utf8>>} | Out]);
['bsr' | In@78] ->
do_to_tokens(In@78, [{operator, <<"bsr"/utf8>>} | Out]);
['not' | In@79] ->
do_to_tokens(In@79, [{operator, <<"not"/utf8>>} | Out]);
['and' | In@80] ->
do_to_tokens(In@80, [{operator, <<"and"/utf8>>} | Out]);
['or' | In@81] ->
do_to_tokens(In@81, [{operator, <<"or"/utf8>>} | Out]);
['xor' | In@82] ->
do_to_tokens(In@82, [{operator, <<"xor"/utf8>>} | Out]);
['andalso' | In@83] ->
do_to_tokens(In@83, [{operator, <<"andalso"/utf8>>} | Out]);
['orelse' | In@84] ->
do_to_tokens(In@84, [{operator, <<"orelse"/utf8>>} | Out]);
[double_plus | In@85] ->
do_to_tokens(In@85, [{operator, <<"++"/utf8>>} | Out]);
[double_minus | In@86] ->
do_to_tokens(In@86, [{operator, <<"--"/utf8>>} | Out]);
[question_equal | In@87] ->
do_to_tokens(In@87, [{operator, <<"?="/utf8>>} | Out]);
[bang | In@88] ->
do_to_tokens(In@88, [{operator, <<"!"/utf8>>} | Out]);
[equal | In@89] ->
do_to_tokens(In@89, [{operator, <<"="/utf8>>} | Out]);
[{unknown, Char@1} | In@90] ->
do_to_tokens(In@90, [{other, Char@1} | Out]);
[{unterminated_string, Contents@5} | In@91] ->
do_to_tokens(
In@91,
[{string, <<"\""/utf8, Contents@5/binary>>} | Out]
);
[{unterminated_sigil, Sigil@3, Delimiter@1, Contents@6} | In@92] ->
do_to_tokens(
In@92,
[{string,
begin
{Opening@1, _} = pearl@token:sigil_delimiters(
Delimiter@1
),
<<<<<<"~"/utf8, Sigil@3/binary>>/binary,
Opening@1/binary>>/binary,
Contents@6/binary>>
end} |
Out]
);
[{unterminated_atom, Contents@7} | In@93] ->
do_to_tokens(In@93, [{atom, <<"'"/utf8, Contents@7/binary>>} | Out]);
[{invalid_triple_quoted_string, Contents@8} | In@94] ->
do_to_tokens(
In@94,
[{string,
<<<<"\"\"\""/utf8, Contents@8/binary>>/binary,
"\"\"\""/utf8>>} |
Out]
)
end.
-file("src/pearl/highlight.gleam", 136).
?DOC(
" Convert a string of Erlang source code into highlighting tokens.\n"
" Highlighting tokens only contain information about the kind of syntax\n"
" being used, grouping similar tokens (e.g. all keywords) into one category.\n"
" \n"
" To convert code into syntax tokens, see `pearl.tokenise`.\n"
).
-spec tokens(binary()) -> list(token()).
tokens(Code) ->
Lexer = pearl:new(Code),
{Tokens, _} = pearl:tokenise(Lexer),
do_to_tokens(Tokens, []).
-file("src/pearl/highlight.gleam", 45).
?DOC(
" Convert a string of Erlang source code into ansi highlighting.\n"
" \n"
" Colours taken from [`contour`](https://hexdocs.pm/contour):\n"
" | Token | Colour |\n"
" | ---------------------- | ----------- |\n"
" | Keyword | Yellow |\n"
" | Module | Cyan |\n"
" | Function | Blue |\n"
" | Operator | Magenta |\n"
" | Comment | Italic grey |\n"
" | String, Number, Atom | Green |\n"
" | Whitespace, Variable | No colour |\n"
"\n"
" If you wish to use other colours or another format, use `to_tokens`.\n"
).
-spec ansi(binary()) -> binary().
ansi(Code) ->
_pipe = tokens(Code),
gleam@list:fold(
_pipe,
<<""/utf8>>,
fun(Code@1, Token) -> <<Code@1/binary, (case Token of
{whitespace, S} ->
gleam_community@ansi:reset(S);
{keyword, S@1} ->
gleam_community@ansi:yellow(S@1);
{variable, S@2} ->
gleam_community@ansi:reset(S@2);
{string, S@3} ->
gleam_community@ansi:green(S@3);
{atom, S@4} ->
gleam_community@ansi:green(S@4);
{number, S@5} ->
gleam_community@ansi:green(S@5);
{module, S@6} ->
gleam_community@ansi:cyan(S@6);
{function, S@7} ->
gleam_community@ansi:blue(S@7);
{operator, S@8} ->
gleam_community@ansi:magenta(S@8);
{comment, S@9} ->
gleam_community@ansi:italic(
gleam_community@ansi:gray(S@9)
);
{punctuation, S@10} ->
gleam_community@ansi:reset(S@10);
{other, S@11} ->
gleam_community@ansi:reset(S@11)
end)/binary>> end
).
-file("src/pearl/highlight.gleam", 101).
?DOC(
" Convert a string of Erlang source code into an HTML string.\n"
" Each token is wrapped in a `<span>` with a class indicating the type of token.\n"
" \n"
" Class names taken from [`contour`](https://hexdocs.pm/contour):\n"
" | Token | CSS class |\n"
" | ----------- | -------------- |\n"
" | Keyword | hl-keyword |\n"
" | Variable | hl-variable |\n"
" | Module | hl-module |\n"
" | Function | hl-function |\n"
" | Operator | hl-operator |\n"
" | Punctuation | hl-punctuation |\n"
" | Comment | hl-comment |\n"
" | String | hl-string |\n"
" | Atom | hl-atom |\n"
" | Number | hl-number |\n"
" | Whitespace | no class |\n"
"\n"
" Place the output within a `<pre><code>...</code></pre>` and add styling for\n"
" these CSS classes to get highlighting on your website. Here's some CSS you\n"
" could use:\n"
"\n"
" ```css\n"
" pre code .hl-comment { color: #d4d4d4; font-style: italic }\n"
" pre code .hl-function { color: #9ce7ff }\n"
" pre code .hl-keyword { color: #ffd596 }\n"
" pre code .hl-operator { color: #ffaff3 }\n"
" pre code .hl-string { color: #c8ffa7 }\n"
" pre code .hl-number { color: #c8ffa7 }\n"
" pre code .hl-regexp { color: #c8ffa7 }\n"
" pre code .hl-class { color: #ffddfa }\n"
" ```\n"
"\n"
" If you wish to use another format see `to_ansi` or `to_tokens`.\n"
).
-spec html(binary()) -> binary().
html(Code) ->
_pipe = tokens(Code),
gleam@list:fold(_pipe, <<""/utf8>>, fun(Acc, Token) -> case Token of
{whitespace, S} ->
<<Acc/binary, S/binary>>;
{keyword, S@1} ->
<<<<<<Acc/binary, "<span class=hl-keyword>"/utf8>>/binary,
(houdini:escape(S@1))/binary>>/binary,
"</span>"/utf8>>;
{variable, S@2} ->
<<<<<<Acc/binary, "<span class=hl-variable>"/utf8>>/binary,
(houdini:escape(S@2))/binary>>/binary,
"</span>"/utf8>>;
{string, S@3} ->
<<<<<<Acc/binary, "<span class=hl-string>"/utf8>>/binary,
(houdini:escape(S@3))/binary>>/binary,
"</span>"/utf8>>;
{atom, S@4} ->
<<<<<<Acc/binary, "<span class=hl-atom>"/utf8>>/binary,
(houdini:escape(S@4))/binary>>/binary,
"</span>"/utf8>>;
{number, S@5} ->
<<<<<<Acc/binary, "<span class=hl-number>"/utf8>>/binary,
(houdini:escape(S@5))/binary>>/binary,
"</span>"/utf8>>;
{module, S@6} ->
<<<<<<Acc/binary, "<span class=hl-module>"/utf8>>/binary,
(houdini:escape(S@6))/binary>>/binary,
"</span>"/utf8>>;
{function, S@7} ->
<<<<<<Acc/binary, "<span class=hl-function>"/utf8>>/binary,
(houdini:escape(S@7))/binary>>/binary,
"</span>"/utf8>>;
{operator, S@8} ->
<<<<<<Acc/binary, "<span class=hl-operator>"/utf8>>/binary,
(houdini:escape(S@8))/binary>>/binary,
"</span>"/utf8>>;
{comment, S@9} ->
<<<<<<Acc/binary, "<span class=hl-comment>"/utf8>>/binary,
(houdini:escape(S@9))/binary>>/binary,
"</span>"/utf8>>;
{punctuation, S@10} ->
<<<<<<Acc/binary, "<span class=hl-punctuation>"/utf8>>/binary,
(houdini:escape(S@10))/binary>>/binary,
"</span>"/utf8>>;
{other, S@11} ->
<<Acc/binary, S@11/binary>>
end end).