% File: prettytok.sty
% Copyright 2022 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.
\RequirePackage{expl3}
\ProvidesExplPackage{prettytok}{2022/05/28}{0.0.0}{Pretty-print token list}





\@ifpackagelater{expl3}{2022/04/10} % https://github.com/latex3/latex3/blob/main/l3kernel/CHANGELOG.md#2022-04-10
  {
  }

  {

\cs_set_protected:Npn \tl_analysis_map_inline:Nn #1
  { \exp_args:No \tl_analysis_map_inline:nn #1 }
\cs_set_protected:Npn \tl_analysis_map_inline:nn #1
  {
    \__tl_analysis:n {#1}
    \int_gincr:N \g__kernel_prg_map_int
    \exp_args:Nc \__tl_analysis_map:Nn
      { _tl_analysis_map_inline_ \int_use:N \g__kernel_prg_map_int :wNw }
  }
\cs_set_protected:Npn \__tl_analysis_map:Nn #1#2
  {
    \cs_gset_protected:Npn #1 ##1##2##3 {#2}
    \exp_after:wN \__tl_analysis_map:NwNw \exp_after:wN #1
      \g__tl_analysis_result_tl
      \s__tl { ? \tl_map_break: } \s__tl
    \prg_break_point:Nn \tl_map_break:
      { \int_gdecr:N \g__kernel_prg_map_int }
  }
\cs_set_protected:Npn \__tl_analysis_map:NwNw #1 #2 \s__tl #3 #4 \s__tl
  {
    \use_none:n #3
    #1 {#2} {#4} {#3}
    \__tl_analysis_map:NwNw #1
  }

  }

\cs_generate_variant:Nn \EI_append:n {x}
\cs_generate_variant:Nn \int_compare:nNnTF {VNnTF}
\cs_generate_variant:Nn \tl_set:Nn {Nx}
\cs_generate_variant:Nn \int_compare:nNnT {VNnT}
\cs_generate_variant:Nn \cs_if_exist_use:NT {cT}

% ======== define handler functions.
% a few things it can do:
% • \tl_build_gput_right:Nn  to \_EIresult (content being put there must be wrapped in \unexpanded i.e. x-expand to the desired result

\cs_set_protected:Npn \EI_append_raw:n #1 {
	\tl_build_gput_right:Nn \_EIresult {#1}
}

\cs_set_protected:Npn \EI_append:n #1 {
	\EI_append_raw:n {\exp_not:n {#1}}
}


% handler can call this to collect following tokens in the input stream as argument.
% #1: the function to pass argument in.  (arguments are passed as brace-hack-included)
% #2: number of arguments
\cs_set_protected:Npn \EI_collect_arg:Nn #1 #2 {
	\def \_EIcollecting_balanced {#2}
	\def \_EIbalance_level {0}
	\tl_build_gbegin:N \_EIbalanced_content
	\tl_build_gput_right:Nn \_EIbalanced_content {\noexpand #1}
}

\cs_set_protected:Npn \EIhandler_EIexpand {
	\EI_collect_arg:Nn \EI_append:x {1}
}

\cs_set_protected:Npn \EIhandler_EIexecute {
	\EI_collect_arg:Nn \use:n {1}
}

\cs_set_protected:Npn \execinsideprepare:n #1 {
	\tl_build_gbegin:N \_EIresult
	\def \_EIcollecting_balanced {0}

	\tl_analysis_map_inline:nn {#1} {
		% reminder: ##1: token, ##2: char code (int), ##3: cat code (hex digit)

		\int_compare:VNnTF \_EIcollecting_balanced > 0 {
			% collecting content.

			\tl_build_gput_right:Nn \_EIbalanced_content {##1}
			\str_case:nn {##3} {
				{1} { \tl_set:Nx \_EIbalance_level {\_EIbalance_level+1} }
				{2} { \tl_set:Nx \_EIbalance_level {\_EIbalance_level-1} }
			}

			% we check this every time instead of only after egroup tokens...
			% so that if there's exactly one token to be collected it's still correctly passed through
			\int_compare:nNnT {\_EIbalance_level} = {0} {

				% done, call handler function and revert to normal mode.

				\tl_set:Nx \_EIcollecting_balanced {\int_eval:n{\_EIcollecting_balanced-1}}
				\int_compare:VNnT \_EIcollecting_balanced = 0 {
					\tl_build_gend:N \_EIbalanced_content
					\tl_set:Nx \_EIbalanced_content {\_EIbalanced_content}
					%\pretty:V \_EIbalanced_content
					\_EIbalanced_content  % ← the handler function is embedded in here
					%\pretty:n {done}
				}
			}
		} {
			\let \_EIprocessed \c_false_bool

			% check for \EIexecute etc. and handle accordingly.
			\int_compare:nNnT {##2} = {-1} {
				\cs_if_exist_use:cT {
					EIhandler_ \expandafter \cs_to_str:N ##1
				} {
					\let \_EIprocessed \c_true_bool
				}
			}

			% if not, just append to result.
			\bool_if:NF \_EIprocessed {
				\tl_build_gput_right:Nn \_EIresult {##1}
			}
		}
	}

	\tl_build_gend:N \_EIresult
	\tl_set:Nx \_EIresult {\_EIresult}
}

\cs_set_protected:Npn \execinside:n #1 {
	\execinsideprepare:n {#1}
	\_EIresult
}
\let\execinside\execinside:n  % normal-catcode alias

\cs_set_protected:Npn \execinside_set:Nn #1 #2 {
	\execinsideprepare:n {#2}
	\let #1 \_EIresult
}
\let\execinsideSet\execinside_set:Nn

\cs_set_protected:Npn \execinside_gset:Nn #1 #2 {
	\execinsideprepare:n {#2}
	\global \let #1 \_EIresult
}
\let\execinsideGset\execinside_gset:Nn

\cs_generate_variant:Nn \exp_args:NN {Nc}
\cs_generate_variant:Nn \EI_append:n {x}
\cs_generate_variant:Nn \exp_args:Nn {No}
\cs_generate_variant:Nn \EI_append:n {o}
\cs_generate_variant:Nn \tl_put_right:Nn {Nx}
\cs_generate_variant:Nn \use:nn {nV}
\cs_generate_variant:Nn \execinside_set:Nn {NV}
	

\def \_precattlappend_space #1 {
	\begingroup
	\lccode `\ =#1
	\lowercase{
		\def\_precattltmp{\endgroup \EI_append:n {~}}
	}
	\_precattltmp
}

\begingroup


\global\def \_precattlappend_begin #1 {
	\begingroup
	\lccode `\{=#1
	\lowercase{\endgroup \EI_append_raw:n { \iftrue { \else } \fi }}
}
\global\def \_precattlappend_end #1 {
	\begingroup
	\lccode `\}=#1
	\lowercase{\endgroup \EI_append_raw:n { \iffalse { \else } \fi }}
}
\endgroup
	
\def \_precattldo_special_cC #1 { \exp_args:Nc \EI_append:n {#1} }
\def \_precattldo_special_cB #1 { \str_map_inline:nn {#1} { \_precattlappend_begin {`##1} } }
\def \_precattldo_special_cE #1 { \str_map_inline:nn {#1} { \_precattlappend_end {`##1} } }
\def \_precattldo_special_cM #1 { \str_map_inline:nn {#1} { \EI_append:x {\char_generate:nn {`##1} {3}} } }
\def \_precattldo_special_cT #1 { \str_map_inline:nn {#1} { \EI_append:x {\char_generate:nn {`##1} {4}} } }
\def \_precattldo_special_cP #1 { \str_map_inline:nn {#1} { \exp_args:No \EI_append:o {\char_generate:nn {`##1} {6}} } }
\def \_precattldo_special_cU #1 { \str_map_inline:nn {#1} { \EI_append:x {\char_generate:nn {`##1} {7}} } }
\def \_precattldo_special_cD #1 { \str_map_inline:nn {#1} { \EI_append:x {\char_generate:nn {`##1} {8}} } }
\def \_precattldo_special_cS #1 { \str_map_inline:nn {#1} { \_precattlappend_space {`##1} } }
\def \_precattldo_special_cL #1 { \str_map_inline:nn {#1} { \EI_append:x {\char_generate:nn {`##1} {11}} } }
\def \_precattldo_special_cO #1 { \str_map_inline:nn {#1} { \EI_append:x {\char_generate:nn {`##1} {12}} } }
\def \_precattldo_special_cA #1 { \str_map_inline:nn {#1} {
		\exp_args:No \EI_append:o {\char_generate:nn {`##1} {13}}
} }



\def \_precattldo_special #1 #2 {
	% given #1 e.g. \cO, #2 e.g. {abc}, append <abc> in OTHER catcode to EI.
	{
		\escapechar=-1

		% let \_precattltmp be the concatenation of \string of all tokens within #2.
		\def \_precattltmp {}
		\tl_analysis_map_inline:nn {#2} {
			\tl_put_right:Nx \_precattltmp {\exp_after:wN \string ##1}
		}

		% then pass it to the specific handler.
		\exp_args:Nc \exp_args:NV {_precattldo_special_\string #1} \_precattltmp
	}
}

\def \_precattldo_special_cC_outer { \_precattldo_special\cC }
\def \_precattldo_special_cB_outer { \_precattldo_special\cB }
\def \_precattldo_special_cE_outer { \_precattldo_special\cE }
\def \_precattldo_special_cM_outer { \_precattldo_special\cM }
\def \_precattldo_special_cT_outer { \_precattldo_special\cT }
\def \_precattldo_special_cP_outer { \_precattldo_special\cP }
\def \_precattldo_special_cU_outer { \_precattldo_special\cU }
\def \_precattldo_special_cD_outer { \_precattldo_special\cD }
\def \_precattldo_special_cS_outer { \_precattldo_special\cS }
\def \_precattldo_special_cL_outer { \_precattldo_special\cL }
\def \_precattldo_special_cO_outer { \_precattldo_special\cO }
\def \_precattldo_special_cA_outer { \_precattldo_special\cA }

\def \_precattl_tmp_help #1 {
	\def \_precattl_do_frozen_relax { \EI_append:n {#1} }
	% #1 here will be substituted by the frozen relax
}
\expandafter \_precattl_tmp_help \ifnum0=0\fi
\let \_precattl_tmp_help \undefined



\def \precattl_prepare:n #1 {
	\begingroup
		\let \EIhandler_EIexpand \undefined
		\let \EIhandler_EIexecute \undefined
		\def \EIhandler_cC { \EI_collect_arg:Nn \_precattldo_special_cC_outer 1 }
		\def \EIhandler_cB { \EI_collect_arg:Nn \_precattldo_special_cB_outer 1 }
		\def \EIhandler_cE { \EI_collect_arg:Nn \_precattldo_special_cE_outer 1 }
		\def \EIhandler_cM { \EI_collect_arg:Nn \_precattldo_special_cM_outer 1 }
		\def \EIhandler_cT { \EI_collect_arg:Nn \_precattldo_special_cT_outer 1 }
		\def \EIhandler_cP { \EI_collect_arg:Nn \_precattldo_special_cP_outer 1 }
		\def \EIhandler_cU { \EI_collect_arg:Nn \_precattldo_special_cU_outer 1 }
		\def \EIhandler_cD { \EI_collect_arg:Nn \_precattldo_special_cD_outer 1 }
		\def \EIhandler_cS { \EI_collect_arg:Nn \_precattldo_special_cS_outer 1 }
		\def \EIhandler_cL { \EI_collect_arg:Nn \_precattldo_special_cL_outer 1 }
		\def \EIhandler_cO { \EI_collect_arg:Nn \_precattldo_special_cO_outer 1 }
		\def \EIhandler_cA { \EI_collect_arg:Nn \_precattldo_special_cA_outer 1 }
		\let \EIhandler_cFrozenRelax \_precattl_do_frozen_relax
		\execinside_gset:Nn \_precattlvalueg {#1}
	\endgroup
	\let \_precattlvalue \_precattlvalueg
}

\def \precattl_set:Nn #1 #2 {
	\precattl_prepare:n {#2}
	\tl_set_eq:NN #1 \_precattlvalue
}
\let\precattlSet\precattl_set:Nn

\def \precattl_exec:n #1 {
	\precattl_prepare:n {#1}
	\_precattlvalue
}
\let\precattlExec\precattl_exec:n





\RequirePackage{filecontentsdef}

%\sys_if_engine_luatex:T{\directlua{require("prettytok")}}

% usage: \pretty:n, etc.

\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 \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 \iow_now:Nn {NV}

\iow_new:N \__prettyh_file
\exp_args:NNx \providecommand \prettyfilename {pretty-\jobname.html}
\providecommand \prettyrefreshstrategy {4}
\providecommand \prettyrefreshduration {1000}


\cs_new_protected:Npn \pretty:n #1 {

	% ======== first write out the preamble if it isn't written. Only do once. Delete the preamble macro if it's already written.
	\cs_if_exist:NT \prettypreamble {
		\iow_open:NV \__prettyh_file \prettyfilename
		{
			\str_set:NV \prettypreamble \prettypreamble
			\precattl_exec:n {
				\str_replace_all:Nnn \prettypreamble {\cO\^^M} {\cO\^^J}
			}
			\iow_now:NV \__prettyh_file \prettypreamble
			\iow_now:Nx \__prettyh_file {set_refresh_strategy(\prettyrefreshstrategy,\prettyrefreshduration)}
		}
		\cs_undefine:N \prettypreamble
	}

	% ======== the actual content. Collect to one \write to put them on the same line (also maybe for efficiency???)
	\tl_build_begin:N \__prettyh_content
	\tl_build_put_right:Nn \__prettyh_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_build_put_right:Nn \__prettyh_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 \__prettyh_content {\int_eval:n {`####1},}
				}
			}
			\tl_build_put_right:Nn \__prettyh_content {),}
		}
		{
			% token is not control sequence
			\tl_build_put_right:Nn \__prettyh_content {token(##2, "##3"),}
		}
	}
	\tl_build_put_right:Nn \__prettyh_content {)//</script><script>}
	\tl_build_end:N \__prettyh_content
	\iow_now:NV \__prettyh_file \__prettyh_content
}

\cs_new_protected:Npn \pretty:w #1 \prettystop {
	\pretty:n {#1}
}

\cs_generate_variant:Nn \pretty:n {x, o, V}
\let \pretty:N \pretty:n

% normal-catcode alias
\let\prettyN\pretty:n
\let\prettyX\pretty:x
\let\prettyO\pretty:o
\let\prettyV\pretty:V

\cs_generate_variant:Nn \pretty:N {c}

\cs_new_protected:Nn \pretty:nn {
	\pretty:n {#1}
	\pretty:n {#2}
}

\cs_new_protected:Nn \pretty:nnn {
	\pretty:n {#1}
	\pretty:n {#2}
	\pretty:n {#3}
}

\cs_new_protected:Nn \pretty:nnnn {
	\pretty:n {#1}
	\pretty:n {#2}
	\pretty:n {#3}
	\pretty:n {#4}
}

\cs_new_protected:Nn \prettyshow:N {
	\exp_args:No \pretty:n {\meaning #1}
}
\let\prettyshowN\prettyshow:N





\ExplSyntaxOff
\begin{filecontentsdefmacro}{\prettypreamble}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="icon" href="data:,">
<style>
body{
	background-color: black;
	color: white;
}

/* https://stackoverflow.com/questions/7011602/stretching-iframe-in-html5 */
#wrap_all { display: none; position:fixed; width:100%; height:100%; top: 0px; left: 0px; }


iframe{
	position: absolute;
	width: 100%;
	height: 100%;
	display: none;
}
#status{
	flex: 0 0 1.3em;
}

#display_wrap{
	flex: 1 0 auto;
}
#controller{
	flex: 0 0 50px;
}

.control-button{
}



.tokenlist, .fileline{
	border: 1px solid gray;
	height: 2em;
	overflow: scroll;
	white-space: nowrap;
}
.tokenlist::-webkit-scrollbar, .fileline::-webkit-scrollbar { width: 0 !important }

.fileline{
	background-color: #404040;
}

.explicit_char_target{
	background-color: #FF8080;
}
.explicit_char{
	background-color: #606060;
}


.cs, .active_char{
	color: #FFFF00;
}
.cs::before{
	content: "\\";
}
.cs::after{
	content: " ";
}

#popup{
	display: none;
	position: absolute;
	border: 1px solid gray;
	background-color: #000080ff;
}

.cmd__left_brace_cmd, .cmd__right_brace_cmd, .cmd__mac_param_cmd{
	color: #FFAAAA;
}
.cmd__out_param_cmd{
	border: 1px solid #FFAAAA;
}
.out_param_expansion{
	border: 1px solid #FFAAAA;
	background-color: #000080;
}
.cmd__out_param_cmd::before{
	content: "#";
}

.cmd__letter_cmd{
	color: #AAFFAA;
}

.cmd__spacer_cmd{
	color: #808080;
}


input[type=button]{
	color: white;
	background-color: #0000ff;
	border: 0;
}

#log{
	display: flex;
	flex-direction: column-reverse;
	flex: 0 0 30%;
	overflow: scroll;
}
</style>
</head>
<body>

	<div id=output></div>
	<div id=wrap_all>
		<iframe id=iframe src="about:blank" frameborder=0></iframe>
		<iframe id=iframe2 src="about:blank" frameborder=0></iframe>
	</div>
	<script>
"use strict"
function show_html(s){
	return s.replace(/ /g, "␣")
		.replace(/\r|\n/g, "↵")
		.replace(/\t/g, "⇥")
}

output.innerHTML=""

function print_tl(...tokens){
	const tl_div=document.createElement("div")
	tl_div.className="tokenlist"
	for(let token of tokens){
		tl_div.appendChild(token)
	}
	output.appendChild(tl_div)
}

function token(charcode /* single character string in hex */, catcode /* integer */){
	const token_span=document.createElement("span")
	const ch=String.fromCodePoint(charcode)
	token_span.innerText=show_html(ch)
	if(catcode==='D'){
		token_span.className="active_char"
		token_span.title=`active ${ch} # ${charcode}`
	}else{
		const cls={
			1: 'cmd__left_brace_cmd',
			2: 'cmd__right_brace_cmd',
			3: 'cmd__math_shift_cmd',
			4: 'cmd__tab_mark_cmd',
			6: 'cmd__mac_param_cmd',
			7: 'cmd__sup_mark_cmd',
			8: 'cmd__sub_mark_cmd',
			A: 'cmd__spacer_cmd',
			B: 'cmd__letter_cmd',
			C: 'cmd__other_char_cmd',
		}[catcode]
		token_span.className="token "+cls
		token_span.title=`${cls} ${ch} # ${charcode}`
	}
	return token_span
}

function cs(...codepoints){
	const token_span=document.createElement("span")

	const control_sequence_name=String.fromCodePoint(...codepoints)
	//const meaning_str=readstr()

	token_span.innerText=show_html(control_sequence_name)
	//token_span.title=`cs '${control_sequence_name}' → ${meaning_str}`
	token_span.className="cs"

	return token_span
}

var refresh_strategy_already_set=false
function set_refresh_strategy(strategy, duration){
	if(refresh_strategy_already_set) return
	refresh_strategy_already_set=true

	switch(strategy){
		case 0: // do nothing
			return

		case 1: // refresh the sub-iframe
			if(window.parent===window){
				print_tl=function(...x) {}
				//output.style.display="none"
				iframe.src=location.href
				iframe.style.display="block"
				wrap_all.style.display="block"
				setInterval(function(){
					iframe.src=iframe.src
				}, duration)
			}
			return

		case 2: // refresh two sub-iframes
			if(window.parent===window){
				print_tl=function(...x) {}
				//output.style.display="none"
				iframe.src=iframe2.src=location.href
				iframe.style.display="block"
				iframe2.style.display="block"
				wrap_all.style.display="block"
				async function sleep(ms){
					await new Promise(resolve=>setTimeout(resolve, ms))
				}
				(async function(){
					while(true){
						await sleep(duration/2)
						iframe.src=iframe.src

						await sleep(duration/2)
						iframe2.style.zIndex=1
						iframe.style.zIndex=2

						await sleep(duration/2)
						iframe2.src=iframe2.src

						await sleep(duration/2)
						iframe.style.zIndex=1
						iframe2.style.zIndex=2
					}
				})()
			}
			return

		case 3: // refresh whole page
			setTimeout(function(){
				location.reload()
			}, duration)
			return

		case 4: // request own file
			window.addEventListener("load", async function(){
				async function get_code(){
					//let text=await (await fetch(location.href)).text()
					let text=await new Promise(function(resolve){
						let request = new XMLHttpRequest();
						request.responseType = "text"
						request.onload = function() {
							resolve(request.responseText) 
						}
						request.onerror = function() {
							console.log("Error happened. Read documentation for more details.")
						}
						request.open("GET", location.href, true)
						request.send()
					})
					return text.replace(/.*?-end-template()-/s, "")
				}
				let text=await get_code()
				output.innerHTML=""
				try{ eval(text) }catch(e) {}
				setInterval(async function(){
					let text2=await get_code()
					if(text2!==text){
						output.innerHTML=""
						try{ eval(text2) }catch(e) {}
						text=text2
					}
				}, duration)
			})
			return
	}
}
	</script>

<script>
// -end-template-
\end{filecontentsdefmacro}
\ExplSyntaxOn
