This is a Plain TeX (or any other format) general purpose macro \scanthechars
which accepts on input a string of character tokens of catcode 12
(1) and puts them in the same order but with transformed catcodes in a token list register called \replacetoks
.
(1) this update adds two \string
's to the \@@scanthechars
macro, and as a result the input is not anymore restricted to be with catcode 12
tokens only. It may even contain control sequences (but not braced material) which will go through unarmed, and one may convert back and forth between various catcode regimes -- see updated image.
The interface to specify the catcodes is via a macro \replacesetup
which is fetched with a comma separated list of things like \!{3}
which means to transform the character token !
into a catcode 3
character token !
.
The illustrative tests use \detokenize
for simplicity sake, to produce easily catcode 12
tokens, or verbatim output, hence the code needs etex
or pdftex
for compilation. But the macros themselves are strictly Knuthian.
Only catcodes of 3
, 4
, 6
, 7
, 8
, 11
, 12
, 13
are dealt with.
Each use of \scanthechars
resets \replacesetup
: next call to \scanthechars
must follow a renewed \replacesetup
. Since now the input is not restricted to be only with catcode 12
tokens, one may use \scanthechars
many times on the same string. See the code and image for how this is done.
The code:
\catcode`@ 11
\long\def\@gobble #1{}
\long\def\@firstoftwo #1#2{#1}
\long\def\@secondoftwo #1#2{#2}
\newtoks\replacetoks
\def\replacesetup #1{%
% this assumes non nil escapechar
\def\replace@list {}%
\def\replace@do ##1##2%
{\expandafter\def\csname replace@setup@\expandafter
\@gobble\string##1\endcsname {##2}}%
\replace@setup #1,\relax\relax,%
}
\def\replace@setup #1#2,{%
\ifx#1\relax\replace@list
\else
\expandafter\def\expandafter\replace@list\expandafter
{\replace@list \replace@do #1{#2}}%
\expandafter\replace@setup
\fi
}
\def\scanthechars #1{\replacetoks{}%
\edef\replace@restore{\lccode`$=\the\lccode`$
\lccode`^=\the\lccode`^
\lccode`_=\the\lccode`_
\lccode`&=\the\lccode`&
\lccode`\noexpand\#=\the\lccode`\#
\lccode`a=`a \lccode`(=\the\lccode`(
\lccode`\noexpand~=\the\lccode`~\relax
}%
\@scanthechars #1\relax}
\def\@scanthechars #1{\ifx #1\relax\expandafter\@firstoftwo
\else\expandafter\@secondoftwo
\fi
{\def\replace@do ##1##2{\expandafter
\let\csname replace@setup@\expandafter
\@gobble\string##1\endcsname\relax}%
\replace@list % resets to relax everything
\replace@restore}%
{\@@scanthechars #1}%
}
\def\@@scanthechars #1{\expandafter
\ifx\csname replace@setup@\string#1\endcsname\relax
\replacetoks\expandafter{\the\replacetoks #1}%
\else
\ifcase\csname replace@setup@\string#1\endcsname\relax
\or\or\or % catcode=3
\lccode`$=`#1 %$
\lowercase
{\replacetoks\expandafter{\the\replacetoks $}}%$
\or % catcode=4
\lccode`&=`#1
\lowercase
{\replacetoks\expandafter{\the\replacetoks &}}%
\or\or % 6
\lccode`\#=`#1
\lowercase
{\replacetoks\expandafter{\the\replacetoks ##}}%
\or % 7
\lccode`^=`#1
\lowercase
{\replacetoks\expandafter{\the\replacetoks ^}}%
\or % 8
\lccode`_=`#1
\lowercase
{\replacetoks\expandafter{\the\replacetoks _}}%
\or\or\or % 11
\lccode`a=`#1
\lowercase
{\replacetoks\expandafter{\the\replacetoks a}}%
\or % 12
\lccode`(=`#1
\lowercase
{\replacetoks\expandafter{\the\replacetoks (}}%
\or % 13
\lccode`~=`#1
\lowercase
{\replacetoks\expandafter{\the\replacetoks ~}}%
\fi
\fi
\@scanthechars
}
\catcode`@ 12
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% utility macro to get the catcode of a character token
% manages only catcodes 3,4,6,7,8,11,12,13
\def\TheCatcode #1{\ifcat\noexpand#1$3\else% $
\ifcat\noexpand#1&4\else
\ifcat\noexpand#1##6\else
\ifcat\noexpand#1^7\else
\ifcat\noexpand#1_8\else
\ifcat\noexpand#1a11\else
\ifcat\noexpand#1(12\else
\ifcat\noexpand#1\noexpand~13\else not handled
\fi\fi\fi\fi\fi\fi\fi\fi }
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\tt
Test I.
\replacesetup{\#{3},\!{7},\?{8}}
\detokenize{\replacesetup{\#{3},\!{7},\?{8}}}
\edef\x{\string#a!b?c\string#}
\x
\expandafter\scanthechars\expandafter{\x}
\the\replacetoks
\begingroup
\catcode`#=3
\catcode`!=7
\catcode`?=8
\gdef\z{#a!b?c#}
\endgroup
\the\replacetoks
=\z
\edef\y{\the\replacetoks}
\ifx\z\y OK\else WRONG\fi
\bigskip
Test II.
\replacesetup{\a{11},\b{11},\c{11},\\{3}}
\detokenize{\replacesetup{\a{11},\b{11},\c{11},\\{3}}}
\catcode`@ 11
\edef\backslashchar{\expandafter\@gobble\string\\}
\catcode`@ 12
% creates an x with catcode 12 a, b, c
\edef\x{\string\abc\backslashchar}
\x
\expandafter\scanthechars\expandafter{\x}
\edef\y{\the\replacetoks}
\begingroup
\catcode`|=0
\catcode`\\=3
|gdef|z{\abc\}
|endgroup
\y=\z
\ifx\z\y OK\else WRONG\fi
\bigskip
Test III.
\replacesetup{\a{3},\b{7},\c{8},\d{12}}
\detokenize{\replacesetup{\a{3},\b{7},\c{8},\d{12}}}
\expandafter\scanthechars\expandafter {\detokenize{abcd}}% convert to catcode 12
\edef\y {\the\replacetoks}
\def\PrintTheCatcode #1{\string#1 with catcode \TheCatcode #1\par }
% previously, we used xinttools.sty:
% \xintApplyInline{\PrintTheCatcode}{\y}
\def\PrintTheCatcodes #1{\ifx#1\relax\else\PrintTheCatcode#1\expandafter
\PrintTheCatcodes\fi }
\expandafter\PrintTheCatcodes\y\relax
% \replacesetup must be redone each time
\replacesetup{\a{3},\b{7},\c{8},\d{12}}
\expandafter\scanthechars\expandafter {\detokenize{adbdcda}}
\detokenize{adbdcda}->\the\replacetoks
\bigskip
Test IV.
\overfullrule 0pt
\replacesetup {\!{6}}
\scanthechars {!}
\expandafter\meaning\the\replacetoks
\edef\y{\the\replacetoks}
\begingroup
\catcode`!6
\gdef\z{!!}
\endgroup
\ifx\z\y OK\else WRONG\fi
\bigskip
TEST V.
From now on we don't require the input to be with catcode 12 tokens.
\replacesetup{\#{3}, \_{11}, \^{11}, \!{4}, \?{7}, \&{8}, \@{6}}
\detokenize{\replacesetup{\#{3}, \_{11}, \^{11}, \!{4}, \?{7}, \&{8}, \@{6}}}
\scanthechars {\tabskip1ex #\mathstrut @#\hfil!\hfil #@#\hfil!\hfil#@#\cr
\noalign\bgroup\hrule\egroup
1!23!456\cr 7890!a!bc\cr
\noalign\bgroup\hrule\egroup }
\detokenize{\scanthechars {\tabskip1ex #\mathstrut @#\hfil!\hfil #@#\hfil!\hfil#@#\cr
\noalign\bgroup\hrule\egroup
1!23!456\cr 7890!a!bc\cr
\noalign\bgroup\hrule\egroup }
}
\detokenize{\halign{\span\the\replacetoks}}
output:
\medskip
\halign{\span\the\replacetoks}
% \expandafter\PrintTheCatcodes\the\replacetoks\relax
% attention _ is mathematically active!
% in plain.tex \mathcode`\_="8000 % \_
\bigskip
\replacesetup{\#{3}, \_{11}, \^{11}, \!{4}, \?{7}, \&{8}, \@{6}}
\detokenize {\replacesetup{\#{3}, \_{11}, \^{11}, \!{4}, \?{7}, \&{8}, \@{6}}}
\scanthechars {##___^?\bgroup abc\egroup &\bgroup\hbox\bgroup___\egroup\egroup##}
\detokenize {\scanthechars {##___^?\bgroup abc\egroup &\bgroup\hbox\bgroup___\egroup\egroup##}}
\detokenize{\hrule\the\replacetoks\hrule}
output:
\hrule\the\replacetoks\hrule
\medskip
\string_ is mathematically active, the subscript is in an \string\hbox\space
hence the catcode 11 version is used, but the first three use \string\_ as
defined in plain.tex for the math active \string_, which now has catcode letter
\bigskip
Let's now go back to catcode 12:
\edef\y{\the\replacetoks}
\replacesetup {\#{12}, \_{12}, \^{12}, \!{12}, \?{12}, \&{12}, \@{12}}
\expandafter\scanthechars\expandafter{\y}
\detokenize{\edef\y{\the\replacetoks}}
\detokenize{\replacesetup {\#{12}, \_{12}, \^{12}, \!{12}, \?{12}, \&{12}, \@{12}}}
\detokenize{\expandafter\scanthechars\expandafter{\y}}
\detokenize{\the\replacetoks}
\the\replacetoks
\edef\z{\the\replacetoks}
\detokenize{\edef\z{\the\replacetoks}\meaning\z}
\meaning\z
\nopagenumbers
\bye
\scantokens
allowed?12
(perhaps11
?) character tokens? (I am asking this because\string
ore-TeX
\detokenize
may produce catcode10
spaces, and perhaps this is what you have as input to your catcode changing task).