% File: prettytok.sty % Copyright 2022-2023 user202729 % % This work may be distributed and/or modified under the conditions of the % LaTeX Project Public License (LPPL), either version 1.3c of this license or % (at your option) any later version. The latest version of this license is in % the file: % % http://www.latex-project.org/lppl.txt % % This work has the LPPL maintenance status `maintained'. % % The Current Maintainer of this work is user202729. \ProvidesExplPackage{prettytok}{2023-04-18}{0.2.0}{Pretty-print token list} \RequirePackage{precattl} \RequirePackage{l3keys2e} \keys_define:nn{prettytok}{ mode.choices:nn={ term-8bit, term-shell, html }{ \str_set:Nx \_prettytok_mode {\l_keys_choice_tl} }, mode.initial:n = { term-8bit }, ignore-tlanalysis-bug.bool_set:N=\_prettytok_ignore_tlanalysis_bug, term-shell-decode-cmd.tl_set:N=\_prettytok_term_shell_decode_cmd, term-shell-decode-cmd.initial:n={}, term-shell-decode-cmd-print.bool_set:N=\_prettytok_term_shell_decode_cmd_print, html-file-name.tl_set:N=\_prettytok_html_file_name, html-file-name.initial:x={pretty-\jobname.html}, html-refresh-strategy.tl_set:N=\_prettytok_html_refresh_strategy, html-refresh-strategy.initial:n=4, html-refresh-duration.tl_set:N=\_prettytok_html_refresh_duration, html-refresh-duration.initial:n=1000, term-prefix.tl_set:N=\prettytermprefix, term-prefix.initial:n={>~}, term-prefix-more.tl_set:N=\prettytermprefixmore, term-prefix-more.initial:n={>~ ..~}, term-wrap-limit.tl_set:N=\prettytermwraplimit, term-wrap-limit.initial:n=70, } \ProcessKeysOptions{prettytok} % ======== check tl_analysis bug. \bool_if:NF \_prettytok_ignore_tlanalysis_bug { \tl_set:Nn \_prettytok_test {} \tl_analysis_map_inline:nn {ab} { \tl_analysis_map_inline:nn {cd} { \tl_put_right:Nx \_prettytok_test {#1 ##1 .} } } \tl_if_eq:NnF \_prettytok_test {ac.ad.bc.bd.} { \msg_new:nnn {prettytok} {tl-analysis-bug} { Because~of~an~tl-analysis~bug~(see~https://github.com/latex3/latex3/issues/1073),~ functions~will~not~be~usable~inside~tl-analysis~functions!~ Upgrade~your~LaTeX~version,~or~ pass-option~"ignore-tlanalysis-bug"~to~ignore~this~error. } \msg_error:nn {prettytok} {tl-analysis-bug} } } % ======== load Lua module. \sys_if_engine_luatex:T{ \directlua{require("prettytok")} \precattl_exec:n { \directlua{prettyprint_frozenrelaxtok=token.get_next().tok}\cFrozenRelax } } % ======== main code start. \cs_generate_variant:Nn \exp_args:NNn {NNx} \cs_generate_variant:Nn \iow_open:Nn {NV} \cs_generate_variant:Nn \exp_args:NNn {NNV} \cs_generate_variant:Nn \iow_now:Nn {Nx} \cs_generate_variant:Nn \tl_if_eq:nnF {xoF} \cs_generate_variant:Nn \tl_if_eq:nnTF {o} \cs_generate_variant:Nn \use:N {c} \cs_generate_variant:Nn \str_map_inline:nn {xn} \cs_generate_variant:Nn \tl_build_put_right:Nn {Nx} \cs_generate_variant:Nn \tl_build_put_right:Nn {NV} \cs_generate_variant:Nn \iow_now:Nn {NV} \str_if_eq:VnTF \_prettytok_mode {html} { \iow_new:N \_prettytok_file \ior_new:N \_prettytok_input_template_file \iow_open:NV \_prettytok_file \_prettytok_html_file_name \precattl_exec:n{ % write the template \ior_open:Nn \_prettytok_input_template_file {prettytok_template.html} \ior_str_map_variable:NNn \_prettytok_input_template_file \_prettytok_line { \str_replace_all:Nnn \_prettytok_line {\cO\^^I} {~} % workaround: XeTeX prints literal tab character as ^^I which obviously breaks JavaScript \iow_now:NV \_prettytok_file \_prettytok_line } \ior_close:N \_prettytok_input_template_file % refresh strategy \iow_now:Nx \_prettytok_file {set_refresh_strategy(\_prettytok_html_refresh_strategy,\_prettytok_html_refresh_duration)} \cs_new_protected:Npn \pretty:n #1 { % ======== the actual content. Collect to one \write to put them on the same line (also maybe for efficiency???) \tl_build_begin:N \_prettytok_content \tl_build_put_right:Nn \_prettytok_content {print_tl(} \tl_analysis_map_inline:nn {#1} { % by documentation of \tl_analysis_map_inline:nn: #1=token, #2=char code, #3=catcode \int_compare:nNnTF {##2} = {-1} { % token is control sequence \tl_if_eq:onTF {##1} {\cFrozenRelax} { \tl_build_put_right:Nn \_prettytok_content {csfrozenrelax(),} } { \tl_build_put_right:Nn \_prettytok_content {cs(} \tl_if_eq:xoF {\use:c {}} {##1} { \str_map_inline:xn {\exp_after:wN \cs_to_str:N ##1} { % side note, must use str here to preserve spaces \tl_build_put_right:Nx \_prettytok_content {\int_eval:n {`####1},} } } \tl_build_put_right:Nn \_prettytok_content {),} } } { % token is not control sequence \tl_build_put_right:Nn \_prettytok_content {token(##2, "##3"),} } } \tl_build_put_right:Nn \_prettytok_content {)//</script><script>} \tl_build_end:N \_prettytok_content \iow_now:NV \_prettytok_file \_prettytok_content } } } { \str_if_eq:VnTF \_prettytok_mode {term-shell} { \newwrite \_prettytok_typeout_stream \tl_if_empty:NTF \_prettytok_term_shell_decode_cmd { \sys_get_shell:nnN {kpsewhich~ prettytok-decode-8bit.py} {\cctab_select:N \c_other_cctab} \_prettytok_term_shell_decode_cmd } { \begingroup \escapechar=-1~ \str_gset:NV \_prettytok_term_shell_decode_cmd \_prettytok_term_shell_decode_cmd \endgroup } \bool_if:NT \_prettytok_term_shell_decode_cmd_print { \typeout{^^JThe~ value~ of~ term-shell-decode-cmd~ is:~ [[[\_prettytok_term_shell_decode_cmd]]]^^J} } \immediate \openout \_prettytok_typeout_stream = {|\_prettytok_term_shell_decode_cmd} } { \int_const:Nn \_prettytok_typeout_stream {128} % 16 works except on LuaLaTeX } \precattl_exec:n{ \str_gset:Nn \_prettytok_gray {\cO\^^[[90m} \str_gset:Nn \_prettytok_red {\cO\^^[[31m} \str_gset:Nn \_prettytok_green {\cO\^^[[38;5;121m} % this is the color vim use for green on my machine. Anyway~ %\str_gset:Nn \_prettytok_green {\cO\^^[[92m} \str_gset:Nn \_prettytok_yellow {\cO\^^[[93m} %\str_gset:Nn \_prettytok_white {\cO\^^[[0m} % i.e. the default \str_gset:Nn \_prettytok_white {\cO\^^[[97m} % vim terminal has a quirk that if a line wraps, the status at the start of the line is considered "default" for that line, so need to explicitly specify color as white % https://stackoverflow.com/q/4842424/5267751 \cs_new_protected:Npn \_prettytok_setcolor:N #1 { \ifx \_prettytok_current_color #1 \else \let \_prettytok_current_color #1 \tl_build_put_right:NV \_prettytok_content #1 \fi } % caller should call \_prettytok_check_wrap: when useful. \cs_new_protected:Npn \_prettytok_term_prepare_print_char:n #1 { \_prettytok_append_content:x { \ifnum #1 < 27 ~ \ifnum #1 = 9 ~ % tab \cO{���} \else \cO{\^\^} \char_generate:nn {#1+64} {12} \bool_if:nT {\int_compare_p:nNn {#1} = {13} || \int_compare_p:nNn {#1} = {10}} { \cO{^^J} \prettytermprefixmore } \fi \else \ifnum #1 = 32 ~ \cO{���} \else \bool_if:nTF {\int_compare_p:nNn {#1} > {32} && \int_compare_p:nNn {#1} < {127}} { % printable ASCII character \char_generate:nn {#1} {12} } { \ifnum #1 < "100 ~ ^ ^ \expandafter \@gobble \exp:w \exp_end_continue_f:w \int_to_hex:n {"100+#1} % convert to hex, exactly 2 digits (by adding 100 then delete first '1') \else \ifnum #1 < "10000 ~ ^ ^ ^ ^ \expandafter \@gobble \exp:w \exp_end_continue_f:w \int_to_hex:n {"10000+#1} % convert to hex, exactly 4 digits \else ^ ^ ^ ^ ^ ^ \expandafter \@gobble \exp:w \exp_end_continue_f:w \int_to_hex:n {"1000000+#1} % convert to hex, exactly 6 digits \fi \fi } \fi \fi } } \cs_generate_variant:Nn \_prettytok_term_prepare_print_char:n {x} \int_new:N \_prettytok_content_len % only call this on content that takes visible width. For example don't use this to set color \cs_new_protected:Npn \_prettytok_append_content:n #1 { \tl_build_put_right:Nn \_prettytok_content {#1} \int_add:Nn \_prettytok_content_len {\str_count:n {#1}} } \cs_generate_variant:Nn \_prettytok_append_content:n {V,x} \cs_new_protected:Npn \_prettytok_check_wrap: { \int_compare:nNnT {\_prettytok_content_len} > {\prettytermwraplimit} { % be careful to not color the \prettytermprefixmore part \ifx \_prettytok_current_color \_prettytok_white \else \tl_build_put_right:NV \_prettytok_content \_prettytok_white \fi \tl_build_end:N \_prettytok_content \_prettytok_typeout_content: \tl_build_begin:N \_prettytok_content \int_zero:N \_prettytok_content_len \_prettytok_append_content:V \prettytermprefixmore \ifx \_prettytok_current_color \_prettytok_white \else \tl_build_put_right:NV \_prettytok_content \_prettytok_current_color \fi } } % \_prettytok_content must have been called \tl_build_end:N on before this function is called \sys_if_engine_luatex:TF { \cs_new_protected:Npn \_prettytok_typeout_content: { \directlua{ texio.write_nl("") print(token.get_macro("_prettytok_content")) } % https://tex.stackexchange.com/questions/64462/lualatex-print-directlua-env-and-newlinehttps://tex.stackexchange.com/questions/64462/lualatex-print-directlua-env-and-newline } } { \cs_new_protected:Npn \_prettytok_typeout_content: { \immediate\write\_prettytok_typeout_stream{\_prettytok_content} } } \cs_new_protected:Npn \pretty:n #1 { \tl_build_begin:N \_prettytok_content \int_zero:N \_prettytok_content_len \_prettytok_append_content:V \prettytermprefix \let \_prettytok_current_color \_prettytok_white \tl_analysis_map_inline:nn {#1} { % by documentation of \tl_analysis_map_inline:nn: #1=token, #2=char code, #3=catcode \int_compare:nNnTF {##2} = {-1} { % token is control sequence \tl_if_eq:onTF {##1} {\cFrozenRelax} { \_prettytok_setcolor:N \_prettytok_red \_prettytok_append_content:n { \cO\\relax~ } } { \_prettytok_setcolor:N \_prettytok_yellow \_prettytok_append_content:n { \cO\\ } \tl_if_eq:xoF {\use:c {}} {##1} { \str_map_inline:xn {\exp_after:wN \cs_to_str:N ##1} { % side note, must use str here to preserve spaces \_prettytok_term_prepare_print_char:x {\int_eval:n {`####1}} } } \_prettytok_append_content:n {~} \_prettytok_check_wrap: % here we do not wrap in the middle of a csname } } { % token is not control sequence \if ##3 B % letter \_prettytok_setcolor:N \_prettytok_green \else \if ##3 A % spacer \_prettytok_setcolor:N \_prettytok_gray \else \if ##3 D % active \_prettytok_setcolor:N \_prettytok_yellow \else \if ##3 C % other cat \_prettytok_setcolor:N \_prettytok_white \else % something else. \_prettytok_setcolor:N \_prettytok_red \fi \fi \fi \fi \_prettytok_term_prepare_print_char:n {##2} \_prettytok_check_wrap: } } \_prettytok_setcolor:N \_prettytok_white \tl_build_end:N \_prettytok_content \_prettytok_typeout_content: } } } % ======== Lua expandable variant. \sys_if_engine_luatex:T { \cs_new:Npn \prettye:n #1 { \directlua{prettyprint(token.scan_toks())}{#1} } \cs_new:Npn \prettye:w { \directlua{prettyprintw()} {} {} } \cs_new:Npn \prettye:nw #1 { \directlua{prettyprintw()} {#1} {} } \cs_new:Npn \prettye:nnw #1 #2 { \directlua{prettyprintw()} {#1} {#2} } } % ======== w variant. \cs_new_protected:Npn \pretty:w { \pretty_aux:w \empty } \cs_new_protected:Npn \pretty_aux:w #1 \prettystop { \pretty:o { #1 } % use o-expansion to remove the \empty #1 \prettystop } \cs_new:Npn \prettystop {} % ======== other variants. \cs_generate_variant:Nn \pretty:n {x, o, V} \cs_new_eq:NN \pretty:N \pretty:n \cs_generate_variant:Nn \pretty:N {c} \cs_new_protected:Npn \prettythenrun:n #1 { \pretty:n {running~#1} #1 \pretty:n {done} } \cs_new_protected:Npn \prettythenrun:w #1 \prettythenrunstop { \prettythenrun:n {#1} } \cs_new_protected:Nn \prettyshow:N { \pretty:o {\meaning #1} } \cs_generate_variant:Nn \prettyshow:N {c} % ======== generate \pretty:nn, \pretty:nnn, etc. \begingroup \def \_prettytok_tmp:NN #1 #2 { % #1: example \pretty:nn, #2: example \pretty:n (the latter has one more n than the former) \cs_new_protected:Npn #1 ##1 ##2 { #2 {##1 ##2} } } \cs_generate_variant:Nn \_prettytok_tmp:NN {cc} \def \_prettytok_tmp {pretty:n} \int_step_inline:nn {9} { \_prettytok_tmp:cc {\_prettytok_tmp n} {\_prettytok_tmp} \tl_put_right:Nn \_prettytok_tmp {n} } \sys_if_engine_luatex:T { \def \_prettytok_tmp:NN #1 #2 { % #1: example \prettye:nn, #2: example \prettye:n (the latter has one more n than the former) \cs_new:Npn #1 ##1 ##2 { #2 {##1 ##2} } } \def \_prettytok_tmp {prettye:n} \int_step_inline:nn {9} { \_prettytok_tmp:cc {\_prettytok_tmp n} {\_prettytok_tmp} \tl_put_right:Nn \_prettytok_tmp {n} } \def \_prettytok_tmp:NN #1 #2 { % #1: example \prettye:nnnw, #2: example \prettye:nnw (the latter has one more n than the former) \cs_new:Npn #1 ##1 ##2 ##3 { #2 {##1} {##2 ##3} } % here ##1 is the callback (first ���n���), ##2 etc. are the additional-print things } \def \_prettytok_tmp {prettye:nn} \int_step_inline:nn {9} { \_prettytok_tmp:cc {\_prettytok_tmp nw} {\_prettytok_tmp w} \tl_put_right:Nn \_prettytok_tmp {n} } } \endgroup % ======== normal-catcode alias \let\prettyN\pretty:n \let\prettyX\pretty:x \let\prettyO\pretty:o \let\prettyV\pretty:V \let\prettyW\pretty:w \let\prettyeN\prettye:n \let\prettyeW\prettye:w \let\prettyshowN\prettyshow:N \let\prettyshowC\prettyshow:c