12

I want to change the letter-case of a single character argument, and use that changed letter inside a \csname. And I want to do it without an additional package.

For example, I want to pass a macro the letter {S}, and have it execute the macro \csname macros\endcsname, or pass it the letter {T}, and have it execute the macro \csname macrot\endcsname.

In my MWE, I show three approaches:

  1. I didn't expect \lowercase to work, since I recall reading that it is not truly expanded until the last possible moment. Surprisingly, it goes into an \edef okay, but it must not be truly expanded, because it breaks the \csname.

  2. I also formulated an approach where I used \numexpr to add 32 to the ASCII code of the argument and perform a \char upon that result. Like \lowercase, it gives the appearance of being expandable, but it also fails inside the \csname.

  3. I finally achieve success with the stringstrings package. Having written the package, I know the conversion was not pretty... essentially, it is an exhaustive 26-fold check of the form \if #1Aa\else\if #1Bb\else\if #1Cc\else...\fi\fi\fi, which has the virtue that it can be expandably placed into an \edef, and so it works.

There has to be a better way. The MWE works only because I use the inefficiently calculated \tmp from METHOD 3 inside the \csname. If I use \tmp from either of the two earlier methods, it breaks.

\documentclass{article}
\usepackage{stringstrings}
\def\macros{Expandably converted an S to a s}
\def\macrot{Expandably converted a T to a t}
\newcommand\convert[1]{%
  %MAKE THE ARGUMENT LOWER CASE
  1) LOWERCASE\\
  \edef\tmp{\lowercase{#1}}% PRETENDS TO EXPAND, BUT FAILS IN \csname
  Here is the ``expanded'' conversion: \tmp\\
  % OR
  2) ASCII CONVERSION\\
  \edef\tmp{\expandafter\char\numexpr`#1+32\relax}% PRETENDS TO EXPAND, BUT FAILS IN \csname
  Here is the ``expanded'' conversion: \tmp\\
  % OR
  3) STRINGSTRINGS PACKAGE\\
  \caselower[q]{#1}\def\tmp{\thestring}% WORKS BUT USES A PACKAGE
  Here is the ``expanded'' conversion: \tmp\\
  %
  Now use it in a csname: \csname macro\tmp\endcsname%
}
\begin{document}
\convert{S}\par
\convert{T}
\end{document}

enter image description here


SUMMARY OF ANSWERS:

I have added egreg's code snippets into this MWE to produce a document (and not just terminal output), showing the methods put forth to this point:

\documentclass{article}

%%%%%%%%%%%%%%%%%%%%%%%%%%%% L3 APPROACH

\usepackage{expl3}

\ExplSyntaxOn
\cs_set_eq:NN \Xexplower \tl_expandable_lowercase:n
\ExplSyntaxOff

%%%%%%%%%%%%%%%%%%%%%%%%%%%% EGREG'S LESS HACKY SOLUTION

\def\explowerchar#1{%
  \ifcase\numexpr`#1-`A\relax
   a\or b\or c\or d\or e\or f\or g\or h\or i\or j\or k\or l\or m\or
   n\or o\or p\or q\or r\or s\or t\or u\or v\or w\or x\or y\or z\else
   #1\fi
}

\def\explower#1{%
  \doexplowerchar#1\relax
}

\def\doexplowerchar#1{%
  \ifx#1\relax
  \else
    \explowerchar{#1}\expandafter\doexplowerchar
  \fi
}

%%%%%%%%%%%%%%%%%%%%%%%%%%%% STEVE'S HACKY PACKAGE APPROACH

\usepackage{stringstrings}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\def\macros{Expandably converted an S to a s}
\def\macrot{Expandably converted a T to a t}

\begin{document}

\textbf{Steve's hacky approach}

\caselower[q]{S}\csname macro\thestring\endcsname\par
\caselower[q]{T}\csname macro\thestring\endcsname

\textbf{egreg's less hacky approach}

\edef\lcstring{\explowerchar{S}}\csname macro\lcstring\endcsname\par
\edef\lcstring{\explower{T}}\csname macro\lcstring\endcsname

\textbf{L3 approach}

\edef\lcstring{\Xexplower{S}}\csname macro\lcstring\endcsname\par
\edef\lcstring{\Xexplower{T}}\csname macro\lcstring\endcsname

\end{document}

enter image description here

3
  • My feeling for the original problem, however, is that you should simply define \macrot and \macroT, so the case is not relevant.
    – egreg
    Apr 25, 2014 at 17:16
  • @egreg For a question such as this, one boils the problem to the essence. The real problem at hand references this answer of mine, tex.stackexchange.com/questions/173209/…, in which capital letters are \active. Therefore I need to deal in macros with lowercase letters only in their names (note my use of stringstrings in that answer). Apr 25, 2014 at 17:37
  • 1
    Well, you have discovered why it's better not to activate letters. ;-) However, \string A gives a category code 12 A independently of the current category code of A.
    – egreg
    Apr 25, 2014 at 17:39

4 Answers 4

11

If you know that the input consists only of characters with category code 11 or 12 (or maybe others, but definitely not control sequences), a less horrible hack is

\def\explowerchar#1{%
  \ifcase\numexpr`#1-`A\relax
   a\or b\or c\or d\or e\or f\or g\or h\or i\or j\or k\or l\or m\or
   n\or o\or p\or q\or r\or s\or t\or u\or v\or w\or x\or y\or z\else
   #1\fi
}

\def\explower#1{%
  \doexplowerchar#1\relax
}

\def\doexplowerchar#1{%
  \ifx#1\relax
  \else
    \explowerchar{#1}\expandafter\doexplowerchar
  \fi
}

\edef\lcstring{\explower{AStRiNg?}}
\show\lcstring

Output on the terminal:

> \lcstring=macro:
->astring?.

Needs e-TeX, of course.

There's no way of doing \lowercase expandably in full generality, I'm afraid.

Without reinventing the wheel:

\documentclass{article}
\usepackage{expl3}

\ExplSyntaxOn
\cs_set_eq:NN \explower \text_lowercase:n
\ExplSyntaxOff

\edef\lcstring{\explower{A StRiNg wiTh SpaC\'Es?}}
\show\lcstring

Terminal output:

> \lcstring=macro:
->astring with spac\'es?.
4
  • No, if you use Knuth TeX. But I tested it with pdftex, pdflatex, lualatex and xelatex. Compile from the terminal or use \meaning instead of \show.
    – egreg
    Apr 25, 2014 at 15:46
  • Fascinating. I'm not used to operating in a "terminal condition." Apr 25, 2014 at 15:50
  • @StevenB.Segletes \show uses the same code as error messages, so it can't be used in an interactive session. I added an easier version. ;-)
    – egreg
    Apr 25, 2014 at 15:53
  • The first works fine in Knuth TeX with the help of package intcalc: \input intcalc.sty\relax or \usepackage{intcalc} and the following \ifcase line: \ifcase\intcalcSub{\number`#1}{65} % Apr 25, 2014 at 15:58
8

I know you've asked for a package-free solution, but for reference the following the the approach we are likely to take to this problem in expl3. The 'design brief' here is that case folding (as defined by the Unicode people) should take place to leave 'caseless' data. Clearly this is only fully doable for LuaTeX/XeTeX: the 'fall back' for pdfTeX is to restrict the data used to only ASCII letters. Note that the code here is from Bruno: I tried a simpler approach but the following is more elegant and much faster!


The concept of string case folding is now available in expl3 as \str_foldcase:n as described by the 'design brief' above. For constructing csnames, where case changing may be required, there are also \str_uppercase:n and \str_lowercase:n. Note these are separate from text case changing, also available in and expandable manner in expl3.


Original code (before addition to expl3): note the the current implementation creates the stored data dynamically from UnicodeData.txt and related source files.

The 'business end' of the approach is a set up (using expl3 syntax):

\cs_new:Npn \str_fold_case:n #1
  {
    \exp_after:wN \__str_fold_auxi:w \tl_to_str:n {#1}
    { ~ \c_empty_tl } \__str_fold_end:w ? ~
  }
\cs_new:Npn \__str_fold_auxi:w #1 ~
  {
    \__str_fold_auxii:N #1 { ~ \c_space_tl }
    \__str_fold_auxi:w
  }
\cs_new:Npn \__str_fold_auxii:N #1
  {
    \exp_after:wN \__str_fold_auxiii:NNNNNNNN
    \int_use:N \__int_eval:w 1000000 + `#1 \__int_eval_end: #1
  }
\cs_new:Npn \__str_fold_auxiii:NNNNNNNN #1#2#3#4#5#6#7#8
  {
    \exp_args:NNv \str_case_x:nnF #8
      { c__str_case_#6_X_#7_tl }
      { #8 \exp_after:wN \use_none:n #8 }
    \__str_fold_auxii:N
  }
\cs_new:Npn \__str_fold_end:w ? #1 \__str_fold_auxi:w { }

which is therefore ultimately dependent on \pdfstrcmp (hidden inside \str_case_x:nnF) to do a string comparison. The above also needs data: this can be auto-generated from the Unicode case-folding file and currently comes out as:

\tl_const:cn {c__str_case_0_X_0_tl} {ÈèĬĭƐɛǴǵϨϩҰұԔԕḔḕṸṹỜờᾤ{ὤι}ⒸⓒⰤⱔⲈⲉꙨꙩ}
\tl_const:cn {c__str_case_0_X_1_tl} {ÉéƑƒჍⴭᾥ{ὥι}ⒹⓓⰥⱕⳭⳮ}
\tl_const:cn {c__str_case_0_X_2_tl} {ÊêĮįǶƕΆάϪϫҲҳԖԗḖḗṺṻỞởᾦ{ὦι}ⒺⓔⰦⱖⲊⲋꙪꙫꜲꜳ}
\tl_const:cn {c__str_case_0_X_3_tl} {ËëƓɠǷƿᾧ{ὧι}ⒻⓕⰧⱗ}
\tl_const:cn {c__str_case_0_X_4_tl} {Ììİ{i̇}ƔɣǸǹΈέϬϭҴҵԘԙḘḙṼṽỠỡᾨ{ὠι}ⒼⓖⰨⱘⲌⲍꙬꙭꜴꜵ}
\tl_const:cn {c__str_case_0_X_5_tl} {ÍíΉήᾩ{ὡι}ⒽⓗⰩⱙ}
\tl_const:cn {c__str_case_0_X_6_tl} {ÎîIJijƖɩǺǻΊίϮϯҶҷԚԛḚḛṾṿỢợᾪ{ὢι}ⒾⓘⰪⱚⲎⲏⳲⳳꜶꜷ}
\tl_const:cn {c__str_case_0_X_7_tl} {ÏïƗɨᾫ{ὣι}ⒿⓙⰫⱛ}
\tl_const:cn {c__str_case_0_X_8_tl} {ÐðĴĵƘƙǼǽΌόϰκҸҹԜԝḜḝẀẁỤụὈὀᾬ{ὤι}ⓀⓚⰬⱜⲐⲑꜸꜹ}
\tl_const:cn {c__str_case_0_X_9_tl} {ÑñϱρὉὁᾭ{ὥι}ⓁⓛⰭⱝ}
\tl_const:cn {c__str_case_1_X_0_tl} {ÒòĶķǾǿΎύҺһԞԟḞḟẂẃỦủὊὂᾮ{ὦι}ⓂⓜⰮⱞⲒⲓꜺꜻ}
\tl_const:cn {c__str_case_1_X_1_tl} {ÓóΏώὋὃᾯ{ὧι}Ⓝⓝ}
\tl_const:cn {c__str_case_1_X_2_tl} {ÔôƜɯȀȁΐ{ΐ}ϴθҼҽԠԡḠḡẄẅỨứὌὄⓄⓞⲔⲕꜼꜽꞠꞡ}
\tl_const:cn {c__str_case_1_X_3_tl} {ÕõĹĺƝɲΑαϵεὍὅⓅⓟAa}
\tl_const:cn {c__str_case_1_X_4_tl} {ÖöȂȃΒβҾҿԢԣḢḣẆẇỪừᾲ{ὰι}ⓆⓠⲖⲗꜾꜿꞢꞣBb}
\tl_const:cn {c__str_case_1_X_5_tl} {ĻļƟɵΓγϷϸև{եւ}ᾳ{αι}ⓇⓡCc}
\tl_const:cn {c__str_case_1_X_6_tl} {ØøƠơȄȅΔδӀӏԤԥḤḥẈẉỬửὐ{ὐ}ᾴ{άι}ⓈⓢⲘⲙꝀꝁꞤꞥDd}
\tl_const:cn {c__str_case_1_X_7_tl} {ÙùĽľΕεϹϲӁӂⓉⓣEe}
\tl_const:cn {c__str_case_1_X_8_tl} {ÚúƢƣȆȇΖζϺϻԦԧḦḧẊẋỮữὒ{ὒ}ᾶ{ᾶ}ⓊⓤⲚⲛꝂꝃꞦꞧFf}
\tl_const:cn {c__str_case_1_X_9_tl} {ÛûĿŀΗηӃӄᾷ{ᾶι}ⓋⓥGg}
\tl_const:cn {c__str_case_2_X_0_tl} {ÜüƤƥȈȉΘθѠѡḨḩẌẍỰựὔ{ὔ}ᾸᾰⓌⓦⲜⲝꝄꝅꞨꞩHh}
\tl_const:cn {c__str_case_2_X_1_tl} {ÝýŁłΙιϽͻӅӆᾹᾱⓍⓧIi}
\tl_const:cn {c__str_case_2_X_2_tl} {ÞþƦʀȊȋΚκϾͼѢѣḪḫẎẏỲỳὖ{ὖ}ᾺὰⓎⓨⲞⲟꝆꝇꞪɦJj}
\tl_const:cn {c__str_case_2_X_3_tl} {ß{ss}ŃńƧƨΛλϿͽӇӈΆάⓏⓩKk}
\tl_const:cn {c__str_case_2_X_4_tl} {ȌȍΜμЀѐѤѥḬḭẐẑỴỵᾼ{αι}ⲠⲡꚀꚁꝈꝉLl}
\tl_const:cn {c__str_case_2_X_5_tl} {ŅņƩʃΝνЁёӉӊὙὑMm}
\tl_const:cn {c__str_case_2_X_6_tl} {ȎȏΞξЂђѦѧḮḯẒẓỶỷιιⲢⲣꚂꚃꝊꝋNn}
\tl_const:cn {c__str_case_2_X_7_tl} {ŇňΟοЃѓӋӌὛὓOo}
\tl_const:cn {c__str_case_2_X_8_tl} {ƬƭȐȑΠπЄєѨѩḰḱẔẕỸỹⲤⲥꚄꚅꝌꝍPp}
\tl_const:cn {c__str_case_2_X_9_tl} {ʼn{ʼn}ΡρЅѕӍӎԱաὝὕQq}
\tl_const:cn {c__str_case_3_X_0_tl} {ŊŋƮʈȒȓІіѪѫԲբḲḳẖ{ẖ}Ỻỻῂ{ὴι}ⲦⲧꚆꚇꝎꝏRr}
\tl_const:cn {c__str_case_3_X_1_tl} {ƯưΣσЇїԳգẗ{ẗ}Ὗὗῃ{ηι}Ss}
\tl_const:cn {c__str_case_3_X_2_tl} {ŌōȔȕΤτЈјѬѭӐӑԴդḴḵẘ{ẘ}Ỽỽῄ{ήι}ⲨⲩꚈꚉꝐꝑTt}
\tl_const:cn {c__str_case_3_X_3_tl} {ƱʊΥυЉљԵեẙ{ẙ}Uu}
\tl_const:cn {c__str_case_3_X_4_tl} {ŎŏƲʋȖȗΦφЊњѮѯӒӓԶզḶḷẚ{aʾ}Ỿỿῆ{ῆ}ⲪⲫꚊꚋꝒꝓVv}
\tl_const:cn {c__str_case_3_X_5_tl} {ƳƴΧχЋћԷէẛṡῇ{ῆι}Ww}
\tl_const:cn {c__str_case_3_X_6_tl} {ŐőȘșΨψЌќѰѱӔӕԸըḸḹῈὲⲬⲭꚌꚍꝔꝕXx}
\tl_const:cn {c__str_case_3_X_7_tl} {ƵƶͅιΩωЍѝԹթΈέYy}
\tl_const:cn {c__str_case_3_X_8_tl} {ŒœȚțΪϊЎўѲѳӖӗԺժḺḻẞ{ss}ῊὴⲮⲯꚎꚏꝖꝗZz}
\tl_const:cn {c__str_case_3_X_9_tl} {ƷʒΫϋЏџԻիΉή}
\tl_const:cn {c__str_case_4_X_0_tl} {ŔŕƸƹȜȝАаѴѵӘәԼլḼḽẠạὨὠῌ{ηι}ⲰⲱꚐꚑꝘꝙ}
\tl_const:cn {c__str_case_4_X_1_tl} {БбԽխὩὡ}
\tl_const:cn {c__str_case_4_X_2_tl} {ŖŗȞȟВвѶѷӚӛԾծḾḿẢảὪὢⲲⲳꚒꚓꝚꝛ}
\tl_const:cn {c__str_case_4_X_3_tl} {ГгԿկὫὣ}
\tl_const:cn {c__str_case_4_X_4_tl} {ŘřƼƽȠƞΰ{ΰ}ДдѸѹӜӝՀհṀṁẤấἈἀὬὤⅠⅰⲴⲵꚔꚕꝜꝝ}
\tl_const:cn {c__str_case_4_X_5_tl} {ЕеՁձἉἁὭὥⅡⅱ}
\tl_const:cn {c__str_case_4_X_6_tl} {ŚśȢȣЖжѺѻӞӟՂղṂṃẦầἊἂὮὦῒ{ῒ}ⅢⅲⲶⲷꚖꚗꝞꝟ}
\tl_const:cn {c__str_case_4_X_7_tl} {ЗзՃճἋἃὯὧΐ{ΐ}Ⅳⅳ}
\tl_const:cn {c__str_case_4_X_8_tl} {ŜŝȤȥИиѼѽӠӡՄմṄṅẨẩἌἄⅤⅴⲸⲹꝠꝡ}
\tl_const:cn {c__str_case_4_X_9_tl} {ЙйՅյἍἅⅥⅵ}
\tl_const:cn {c__str_case_5_X_0_tl} {ŞşȦȧКкѾѿӢӣՆնṆṇẪẫἎἆῖ{ῖ}ⅦⅶⲺⲻꝢꝣ}
\tl_const:cn {c__str_case_5_X_1_tl} {ЛлՇշἏἇῗ{ῗ}Ⅷⅷ}
\tl_const:cn {c__str_case_5_X_2_tl} {ŠšDŽdžȨȩМмҀҁӤӥՈոṈṉẬậῘῐⅨⅸⲼⲽꝤꝥ}
\tl_const:cn {c__str_case_5_X_3_tl} {DždžНнՉչῙῑⅩⅹ}
\tl_const:cn {c__str_case_5_X_4_tl} {ŢţȪȫОоӦӧՊպṊṋẮắῚὶⅪⅺⲾⲿꝦꝧ}
\tl_const:cn {c__str_case_5_X_5_tl} {LJljПпՋջΊίⅫⅻ}
\tl_const:cn {c__str_case_5_X_6_tl} {ĀāŤťLjljȬȭРрӨөՌռႠⴀṌṍẰằⅬⅼⳀⳁꝨꝩff{ff}}
\tl_const:cn {c__str_case_5_X_7_tl} {СсՍսႡⴁⅭⅽfi{fi}}
\tl_const:cn {c__str_case_5_X_8_tl} {ĂăŦŧNJnjȮȯТтӪӫՎվႢⴂṎṏẲẳⅮⅾⳂⳃꝪꝫfl{fl}}
\tl_const:cn {c__str_case_5_X_9_tl} {NjnjУуՏտႣⴃⅯⅿffi{ffi}}
\tl_const:cn {c__str_case_6_X_0_tl} {ĄąŨũȰȱФфӬӭՐրႤⴄṐṑẴẵἘἐⱠⱡⳄⳅꙀꙁꝬꝭffl{ffl}𐐀𐐨}
\tl_const:cn {c__str_case_6_X_1_tl} {ǍǎХхՑցႥⴅἙἑſt{st}𐐁𐐩}
\tl_const:cn {c__str_case_6_X_2_tl} {ĆćŪūȲȳςσЦцҊҋӮӯՒւႦⴆṒṓẶặἚἒῢ{ῢ}ⱢɫⳆⳇꙂꙃꝮꝯst{st}𐐂𐐪}
\tl_const:cn {c__str_case_6_X_3_tl} {ǏǐЧчՓփႧⴇἛἓΰ{ΰ}Ᵽᵽ𐐃𐐫}
\tl_const:cn {c__str_case_6_X_4_tl} {ĈĉŬŭШшҌҍӰӱՔքႨⴈṔṕẸẹἜἔᾀ{ἀι}ῤ{ῤ}ⰀⰰⱤɽⳈⳉꙄꙅ𐐄𐐬}
\tl_const:cn {c__str_case_6_X_5_tl} {AaǑǒЩщՕօႩⴉἝἕᾁ{ἁι}Ⰱⰱ𐐅𐐭}
\tl_const:cn {c__str_case_6_X_6_tl} {BbĊċŮůЪъҎҏӲӳՖֆႪⴊṖṗẺẻᾂ{ἂι}ῦ{ῦ}ⰂⰲⳊⳋꙆꙇ𐐆𐐮}
\tl_const:cn {c__str_case_6_X_7_tl} {CcǓǔЫыႫⴋᾃ{ἃι}ῧ{ῧ}ⰃⰳⱧⱨ𐐇𐐯}
\tl_const:cn {c__str_case_6_X_8_tl} {DdČčŰűЬьҐґӴӵႬⴌṘṙẼẽᾄ{ἄι}ῨῠⰄⰴⳌⳍꙈꙉ𐐈𐐰}
\tl_const:cn {c__str_case_6_X_9_tl} {EeǕǖЭэႭⴍᾅ{ἅι}ῩῡⰅⰵⱩⱪ𐐉𐐱}
\tl_const:cn {c__str_case_7_X_0_tl} {FfĎďŲųȺⱥЮюҒғӶӷႮⴎṚṛẾếᾆ{ἆι}ῪὺⰆⰶⳎⳏꙊꙋ𐐊𐐲}
\tl_const:cn {c__str_case_7_X_1_tl} {GgǗǘȻȼЯяႯⴏᾇ{ἇι}ΎύⰇⰷⱫⱬ𐐋𐐳}
\tl_const:cn {c__str_case_7_X_2_tl} {HhĐđŴŵҔҕӸӹႰⴐṜṝỀềᾈ{ἀι}ῬῥⰈⰸⳐⳑꙌꙍ𐐌𐐴}
\tl_const:cn {c__str_case_7_X_3_tl} {IiǙǚȽƚႱⴑᾉ{ἁι}ⰉⰹⱭɑꝹꝺ𐐍𐐵}
\tl_const:cn {c__str_case_7_X_4_tl} {JjĒēŶŷȾⱦҖҗӺӻႲⴒṞṟỂểᾊ{ἂι}ⰊⰺⱮɱⳒⳓꙎꙏ𐐎𐐶}
\tl_const:cn {c__str_case_7_X_5_tl} {KkǛǜϏϗႳⴓᾋ{ἃι}ⰋⰻⱯɐꝻꝼﬓ{մն}𐐏𐐷}
\tl_const:cn {c__str_case_7_X_6_tl} {LlĔĕŸÿϐβҘҙӼӽႴⴔṠṡỄễἨἠᾌ{ἄι}ⰌⰼⱰɒⳔⳕꙐꙑﬔ{մե}𐐐𐐸}
\tl_const:cn {c__str_case_7_X_7_tl} {MmŹźɁɂϑθႵⴕἩἡᾍ{ἅι}ⰍⰽꝽᵹﬕ{մի}𐐑𐐹}
\tl_const:cn {c__str_case_7_X_8_tl} {NnĖėǞǟҚқӾӿႶⴖṢṣỆệἪἢᾎ{ἆι}ῲ{ὼι}ⰎⰾⱲⱳⳖⳗꙒꙓꝾꝿﬖ{վն}𐐒𐐺}
\tl_const:cn {c__str_case_7_X_9_tl} {OoŻżɃƀႷⴗἫἣᾏ{ἇι}ῳ{ωι}ↃↄⰏⰿﬗ{մխ}𐐓𐐻}
\tl_const:cn {c__str_case_8_X_0_tl} {PpĘęǠǡɄʉͰͱҜҝԀԁႸⴘḀḁṤṥỈỉἬἤᾐ{ἠι}ῴ{ώι}ⰐⱀⳘⳙꙔꙕꞀꞁ𐐔𐐼}
\tl_const:cn {c__str_case_8_X_1_tl} {QqµμŽžɅʌϕφႹⴙἭἥᾑ{ἡι}ⰑⱁⱵⱶ𐐕𐐽}
\tl_const:cn {c__str_case_8_X_2_tl} {RrĚěǢǣɆɇͲͳϖπҞҟԂԃႺⴚḂḃṦṧỊịἮἦᾒ{ἢι}ῶ{ῶ}ⰒⱂⳚⳛꙖꙗꞂꞃ𐐖𐐾}
\tl_const:cn {c__str_case_8_X_3_tl} {SsſsႻⴛἯἧᾓ{ἣι}ῷ{ῶι}Ⱃⱃ𐐗𐐿}
\tl_const:cn {c__str_case_8_X_4_tl} {TtĜĝǤǥɈɉϘϙҠҡԄԅႼⴜḄḅṨṩỌọᾔ{ἤι}ῸὸⰔⱄⳜⳝꙘꙙꞄꞅ𐐘𐑀}
\tl_const:cn {c__str_case_8_X_5_tl} {UuƁɓႽⴝᾕ{ἥι}ΌόⰕⱅ𐐙𐑁}
\tl_const:cn {c__str_case_8_X_6_tl} {VvĞğƂƃǦǧɊɋͶͷϚϛҢңԆԇႾⴞḆḇṪṫỎỏᾖ{ἦι}ῺὼΩωⰖⱆⳞⳟꙚꙛꜢꜣꞆꞇ𐐚𐑂}
\tl_const:cn {c__str_case_8_X_7_tl} {WwႿⴟᾗ{ἧι}ΏώⰗⱇ𐐛𐑃}
\tl_const:cn {c__str_case_8_X_8_tl} {XxĠġƄƅǨǩɌɍϜϝҤҥԈԉჀⴠḈḉṬṭỐốᾘ{ἠι}ῼ{ωι}ⰘⱈⳠⳡꙜꙝꜤꜥ𐐜𐑄}
\tl_const:cn {c__str_case_8_X_9_tl} {YyჁⴡᾙ{ἡι}Ⱉⱉ𐐝𐑅}
\tl_const:cn {c__str_case_9_X_0_tl} {ZzĢģƆɔǪǫɎɏϞϟҦҧԊԋჂⴢḊḋṮṯỒồᾚ{ἢι}KkⰚⱊⱾȿⳢⳣꙞꙟꜦꜧ𐐞𐑆}
\tl_const:cn {c__str_case_9_X_1_tl} {ƇƈჃⴣᾛ{ἣι}ÅåⰛⱋⱿɀꞋꞌ𐐟𐑇}
\tl_const:cn {c__str_case_9_X_2_tl} {ÀàĤĥǬǭϠϡҨҩԌԍჄⴤḌḍṰṱỔổἸἰᾜ{ἤι}ⰜⱌⲀⲁꙠꙡꜨꜩ𐐠𐑈}
\tl_const:cn {c__str_case_9_X_3_tl} {ÁáƉɖჅⴥἹἱᾝ{ἥι}ⰝⱍꞍɥ𐐡𐑉}
\tl_const:cn {c__str_case_9_X_4_tl} {ÂâĦħƊɗǮǯϢϣҪҫԎԏḎḏṲṳỖỗἺἲᾞ{ἦι}ⰞⱎⲂⲃꙢꙣꜪꜫ𐐢𐑊}
\tl_const:cn {c__str_case_9_X_5_tl} {ÃãƋƌჇⴧἻἳᾟ{ἧι}Ⱏⱏ𐐣𐑋}
\tl_const:cn {c__str_case_9_X_6_tl} {ÄäĨĩǰ{ǰ}ϤϥҬҭԐԑḐḑṴṵỘộἼἴᾠ{ὠι}ⰠⱐⲄⲅꙤꙥꜬꜭꞐꞑ𐐤𐑌}
\tl_const:cn {c__str_case_9_X_7_tl} {ÅåDZdzἽἵᾡ{ὡι}Ⱑⱑ𐐥𐑍}
\tl_const:cn {c__str_case_9_X_8_tl} {ÆæĪīƎǝDzdzϦϧҮүԒԓḒḓṶṷỚớἾἶᾢ{ὢι}ℲⅎⒶⓐⰢⱒⲆⲇꙦꙧꜮꜯꞒꞓ𐐦𐑎}
\tl_const:cn {c__str_case_9_X_9_tl} {ÇçƏəἿἷᾣ{ὣι}ⒷⓑⰣⱓⳫⳬ𐐧𐑏}

The 'trick' here is to divide up the rather long list of chars into blocks, which means that the string comparison (relatively slow) doesn't have to map over the entire list (over 1000 chars) to find a match. As you'll see, it's only when there is a change available that there is any data to store at all.

The above should get added to expl3 'real soon now', depending on my time availability.

2
  • I didn't realize I was opening a can of worms :^) Apr 25, 2014 at 16:16
  • To paraphrase Otto von Bismarck, "LaTeX3 coding is like sausages, it is better not to see them being made." Apr 25, 2014 at 16:41
3

Since I was playing around with it 9 months after I asked the question, and developed a different approach, I will post it here. It achieves the result by making upper- or lower-cased characters active and then redefining them in the other lettercase.

It achieves the goal of the question, which was "I want to pass a macro the letter {S}, and have it execute the macro \csname macros\endcsname, or pass it the letter {T}, and have it execute the macro \csname macrot\endcsname."

However, it still has limitations, two big ones that come to mind:

  1. I can't operate operate on \def'ed information. Thus, I can say \macrofylc{T} and have it execute \csname macrot\endcsname, but I can't say \def\x{T}\macrofylc{\x}.

  2. It can handle macros in its arguments, but only if the macros are named in letters that are not being made active. Thus, I can \expresslc{This is An \textit{ITALIC} test} to get "this is an italic test", but I to make it upper case, I need the following kludge: \let\TEXTIT\textit\expressuc{This is An \TEXTIT{ITALIC} test} to get "THIS IS AN ITALIC TEST"

Note that limitation #2 is not a whole lot worse than the current state of affairs in LaTeX2e, since we are talking about things that are not regular expressions. However, I would dearly like to remedy limitation #1, but don't see how at this point.

Here is the MWE:

\documentclass{article}
\def\setuc{%
\8101\9`A=\8102\9\8101\9`B=\8102\9\8101\9`C=\8102\9\8101\9`D=\8102\9%
\8101\9`E=\8102\9\8101\9`F=\8102\9\8101\9`G=\8102\9\8101\9`H=\8102\9%
\8101\9`I=\8102\9\8101\9`J=\8102\9\8101\9`K=\8102\9\8101\9`L=\8102\9%
\8101\9`M=\8102\9\8101\9`N=\8102\9\8101\9`O=\8102\9\8101\9`P=\8102\9%
\8101\9`Q=\8102\9\8101\9`R=\8102\9\8101\9`S=\8102\9\8101\9`T=\8102\9%
\8101\9`U=\8102\9\8101\9`V=\8102\9\8101\9`W=\8102\9\8101\9`X=\8102\9%
\8101\9`Y=\8102\9\8101\9`Z=\8102\9%
}
\def\setlc{%
\8101\9`a=\8102\9\8101\9`b=\8102\9\8101\9`c=\8102\9\8101\9`d=\8102\9%
\8101\9`e=\8102\9\8101\9`f=\8102\9\8101\9`g=\8102\9\8101\9`h=\8102\9%
\8101\9`i=\8102\9\8101\9`j=\8102\9\8101\9`k=\8102\9\8101\9`l=\8102\9%
\8101\9`m=\8102\9\8101\9`n=\8102\9\8101\9`o=\8102\9\8101\9`p=\8102\9%
\8101\9`q=\8102\9\8101\9`r=\8102\9\8101\9`s=\8102\9\8101\9`t=\8102\9%
\8101\9`u=\8102\9\8101\9`v=\8102\9\8101\9`w=\8102\9\8101\9`x=\8102\9%
\8101\9`y=\8102\9\8101\9`z=\8102\9%
}
\def\resetuc{%
\8101\9`A=11\8101\9`B=11\8101\9`C=11\8101\9`D=11%
\8101\9`E=11\8101\9`F=11\8101\9`G=11\8101\9`H=11%
\8101\9`I=11\8101\9`J=11\8101\9`K=11\8101\9`L=11%
\8101\9`M=11\8101\9`N=11\8101\9`O=11\8101\9`P=11%
\8101\9`Q=11\8101\9`R=11\8101\9`S=11\8101\9`T=11%
\8101\9`U=11\8101\9`V=11\8101\9`W=11\8101\9`X=11%
\8101\9`Y=11\8101\9`Z=11%
}
\def\resetlc{%
\8101\9`a=11\8101\9`b=11\8101\9`c=11\8101\9`d=11%
\8101\9`e=11\8101\9`f=11\8101\9`g=11\8101\9`h=11%
\8101\9`i=11\8101\9`j=11\8101\9`k=11\8101\9`l=11%
\8101\9`m=11\8101\9`n=11\8101\9`o=11\8101\9`p=11%
\8101\9`q=11\8101\9`r=11\8101\9`s=11\8101\9`t=11%
\8101\9`u=11\8101\9`v=11\8101\9`w=11\8101\9`x=11%
\8101\9`y=11\8101\9`z=11%
}
\let\8\csname
\let\9\endcsname
\expandafter\let\csname101\endcsname\catcode
\expandafter\let\csname102\endcsname\active
\expandafter\let\csname103\endcsname\gdef
\expandafter\let\csname104\endcsname\resetuc
\expandafter\let\csname105\endcsname\resetlc
%%%%%%%%%%%%%%%%%%%%%%%%%
\def\macrofylc{\setuc\macrotransform}
\def\macrofyuc{\setlc\macrotransform}
\def\expresslc{\setuc\transform}
\def\expressuc{\setlc\transform}
\def\deflc{\setuc\deftransform}
\def\defuc{\setlc\deftransform}
%%%%%%%%%%%%%%%%%%%%%%%%%
\def\macrotransform#1{\csname macro#1\endcsname\resetuc\resetlc}
\def\transform#1{#1\resetuc\resetlc}
\def\deftransform#1{\def\thestring{#1}\resetuc\resetlc}
%%%%%%%%%%%%%%%%%%%%%%%%%
\setuc
% REDEFINE uc TO LOWERCASE
\8103\9A{a}\8103\9B{b}\8103\9C{c}\8103\9D{d}\8103\9E{e}\8103\9F{f}%
\8103\9G{g}\8103\9H{h}\8103\9I{i}\8103\9J{j}\8103\9K{k}\8103\9L{l}%
\8103\9M{m}\8103\9N{n}\8103\9O{o}\8103\9P{p}\8103\9Q{q}\8103\9R{r}%
\8103\9S{s}\8103\9T{t}\8103\9U{u}\8103\9V{v}\8103\9W{w}\8103\9X{x}%
\8103\9Y{y}\8103\9Z{z}%
\8104\9% \resetuc
%%%%%%%%%%%%%%%%%%%%%%%%%
\setlc
% REDEFINE LOWERCASE TO uc
\8103\9a{A}\8103\9b{B}\8103\9c{C}\8103\9d{D}\8103\9e{E}\8103\9f{F}%
\8103\9g{G}\8103\9h{H}\8103\9i{I}\8103\9j{J}\8103\9k{K}\8103\9l{L}%
\8103\9m{M}\8103\9n{N}\8103\9o{O}\8103\9p{P}\8103\9q{Q}\8103\9r{R}%
\8103\9s{S}\8103\9t{T}\8103\9u{U}\8103\9v{V}\8103\9w{W}\8103\9x{X}%
\8103\9y{Y}\8103\9z{Z}%
\8105\9% \resetlc
%%%%%%%%%%%%%%%%%%%%%%%%%
% THESE WILL TELL ME IF I HAVE SUCCEEDED OR NOT
\def\macrot{You have executed macrot}
\def\macroT{You have executed macroT}
\def\macrost{You have executed macrost}
\def\macroST{You have executed macroST}
\begin{document}
\textbf{Express directly: lc, then uc}\par
\expresslc{This IS a \textbf{Bold} \textit{Italic} test}\par
\let\TEXTBF\textbf\let\TEXTIT\textit% THIS IS A CHEAT
\expressuc{This IS a \TEXTBF{Bold} \TEXTIT{Italic} test that cheats.}\par

\textbf{Macrofy lc:}\par
\macrofylc{T}\par
\macrofylc{t}\par
\macrofylc{ST}\par
\macrofylc{St}\par
\macrofylc{sT}\par
\textbf{Macrofy uc:}\par
\macrofyuc{T}\par
\macrofyuc{t}\par
\macrofyuc{st}\par
\macrofyuc{sT}\par
\macrofyuc{St}\par

\textbf{Place in def:}\par
\deflc{This IS a \textbf{Bold} CASE}
Here is thestring stored by deflc: \thestring\par
\textit{UPPERCASE and lowercase are restored.}\par
\end{document}

enter image description here

6
  • f expansion (in expl3 terms) is expandable itself, you could apply it to the argument before passing the argument to \macrofylc{\x}. I don't know how exactly is it implemented, but it's the common \romannumeral`\Q.. (or something like that, I don't really know).
    – Manuel
    Jan 16, 2015 at 20:50
  • @Manuel Thanks for the reminder about the \rommannumeral trick. I also don't really understand it, but even a monkey, given enough random tries at the typewriter, could produce Shakespeare's sonnets. Maybe I'll try to be that monkey. Jan 16, 2015 at 21:02
  • Search in expl3-code.tex for \exp_args:Nf and find what does it do (that's exactly what you need, if I understood correctly, so \exp_args:Nf\macrofylc{\x} should work). In any case, I think there are lots of examples in this site about the \romannumeral trick, you don't need that monkey, just a bit of search and trial and error.
    – Manuel
    Jan 16, 2015 at 21:08
  • Isn't it just \expandafter\macrofylc\expandafter{\romannumeral`\Q\x}? (I mean that's the idea, you need to include that in the macro, but that's easy.)
    – Manuel
    Jan 16, 2015 at 21:48
  • 1
    @Manuel The problem seems to be one of catcodes, not expansion. When a \def\x{T} occurs in advance of an invocation to my macro, the "T" in \x is catcode 12. Thus, when passed to my macro, the making of "T" to an active character does not retroactively change the catcode of "T" belonging to \x. Jan 16, 2015 at 22:00
3

All I can do off the cuff is offering a routine \UD@ExpandableLowercase which delivers lowercasing of those 26 catcode-11(letter)-character-tokens that correspond to the 26 letters of the latin alphabet within two expansion steps.

(!!!Be aware that having TeX read and tokenize input consisting of characters of the latin alphabet usually yields catcode-11(letter)-character-tokens while expanding \string, \meaning, \jobmane and the like primitives delivers catcode-12(other)-character-tokens!!!
As an exception to the rule, with \string, \meaning, \jobmane and the like primitives spaces will be catcode-10(space)-charcode-32-space-tokens. )

The routine does not require eTeX or the like extensions.

As a (probably disliked) side effect, the routine does replace pairs of matching catcode-1-character-tokens and catcode-2-character-tokens by pairs of matching curly catcode-1-brace-tokens and curly catcode-2-brace-tokens, i.e. { and }.

I think this should not be a problem with plain TeX and LaTeX2e as with these formats usually the only character with category code 1 is the opening curly brace { and the only character with category code 2 is the closing curly brace }.


enter image description here


\documentclass{article}

\makeatletter
%%-----------------------------------------------------------------------------
%% Paraphernalia:
%%.............................................................................
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@Exchange[2]{#2#1}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@removespace{}\UD@firstoftwo{\def\UD@removespace}{} {}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that
%%                       argument which is to be checked is empty>}%
%%                     {<Tokens to be delivered in case that
%%                       argument which is to be checked is not empty>}%
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral0\expandafter\UD@secondoftwo\string{\expandafter
  \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
  \UD@secondoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's first token is a catcode-1-character
%%.............................................................................
%% \UD@CheckWhetherBrace{<Argument which is to be checked>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked has leading
%%                        catcode-1-token>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked has no leading
%%                        catcode-1-token>}%
\newcommand\UD@CheckWhetherBrace[1]{%
  \romannumeral0\expandafter\UD@secondoftwo\expandafter{\expandafter{%
  \string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
  \UD@firstoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% \UD@CheckWhetherLeadingSpace{<Argument which is to be checked>}%
%%                             {<Tokens to be delivered in case <argument
%%                               which is to be checked>'s 1st token is a
%%                               space-token>}%
%%                             {<Tokens to be delivered in case <argument
%%                               which is to be checked>'s 1st token is not
%%                               a space-token>}%
\newcommand\UD@CheckWhetherLeadingSpace[1]{%
  \romannumeral0\UD@CheckWhetherNull{#1}%
  {\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
  {\expandafter\UD@secondoftwo\string{\UD@CheckWhetherLeadingSpaceB.#1 }{}}%
}%
\newcommand\UD@CheckWhetherLeadingSpaceB{}%
\long\def\UD@CheckWhetherLeadingSpaceB#1 {%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@secondoftwo#1{}}%
  {\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
  {\UD@Exchange{ }{\expandafter\expandafter\expandafter\expandafter
   \expandafter\expandafter\expandafter}\expandafter\expandafter
   \expandafter}\expandafter\UD@secondoftwo\expandafter{\string}%
}%
%%-----------------------------------------------------------------------------
%% \romannumeral\UD@Expandtimes{<number K>}<token sequence>
%%   -> K times the leading token of <token sequence> will be "hit" by
%%      \expandafter .
%%.............................................................................
\newcommand\UD@innerdfork{}\def\UD@innerdfork#1d#2#3dd{#2}%
\newcommand*\UD@dfork[1]{\UD@innerdfork#1{\UD@firstoftwo}d{\UD@secondoftwo}dd}%
\newcommand*\UD@Expandtimes[1]{%
  0\expandafter\UD@innerExp
   \expandafter{\expandafter}\romannumeral\number\number#1 000d%
}
\newcommand*\UD@innerExp[2]{%
  \UD@dfork{#2}{#1 }{\UD@innerExp{#1#1\expandafter}}%
}%
%%-----------------------------------------------------------------------------
%% Fully expandable for-loop:
%%.............................................................................
%% \UD@DoWithEachElementOfArgumentList{{<number K>}<action>}%
%%                                   {{<number L>}{<number M>}<action if element is nested in braces>}%
%%                                   {{<number N>}<action if list empty>}%
%%                                   {<e_k><e_(k+1)>{<e_(k+2)>}..<e_n>}%<-This is the list of elements/undelimited arguments.
%%
%%  yields (after _two_ expansion-steps) :
%%
%%  IF <number L> < 1 THEN:
%%
%%  result of <number K> \expandafter-hits on <action>{<e_k>}%
%%  result of <number K> \expandafter-hits on <action>{<e_(k+1)>}%
%%  result of <number M> \expandafter-hits on <action if element is nested in braces>{<e_(k+2)>}%
%%  ...
%%  result of <number K> \expandafter-hits on <action>{<e_n>}%
%%  result of <number N> \expandafter-hits on <action if list empty>%
%%
%%  IF <number L> >= 1 THEN:
%%
%%  result of <number K> \expandafter-hits on <action>{<e_k>}%
%%  result of <number K> \expandafter-hits on <action>{<e_(k+1)>}%
%%  {result of <number M> \expandafter-hits on <action if element is nested in braces>{<e_(k+1)>}}%
%%  ...
%%  result of <number K> \expandafter-hits on <action>{<e_n>}%
%%  result of <number N> \expandafter-hits on <action if list empty>%
%%
%% The <action if element is nested in braces>-argument can be used for running the
%% \UD@DoWithEachElementOfArgumentList-loop on the single components of elements
%% that consist of more than one token and therefore are nested in curly braces.
%% If <number M> >= 1, braces hereby will be preserved, otherwise they will be removed.
%%
%% <number K>, <number L>, <number N> denote the desired level of expansion.
%%
\newcommand\UD@KeepOnlyFirstBeforeSeLDoM{}%
\long\def\UD@KeepOnlyFirstBeforeSeLDoM#1#2\UD@SeLDoM{{#1}}%
\newcommand\UD@DoWithEachElementOfArgumentList{%
  \romannumeral0\UD@MoveElementFromList{}{ }{}%
}%
%% \UD@MoveElementFromList[7]
%%  Argument 1: {<indicator whether \UD@MoveElementFromList is in the
%%              loop for extracting the first element of the list or is
%%              ready to process the first element of the list and add
%%              the processing-result to <tokens collected so far> >}%
%%              If in the loop for extracting the first element, then
%%              Argument 1 / #1 is not empty.
%%  Argument 2: {<tokens collected so far>}%
%%  Argument 3: {<indicator whether first element of list starts with
%%               catcode-1-opening brace>}. 
%%               If so, argument 3/ #3=".", else argument 3/ #3 empty.
%%  Argument 4: {{<number K>}<action>}%
%%  Argument 5: {{<number L>}{<number M>}<action if element is nested 
%%               in braces>}%
%%  Argument 6: {{<number N>}<action if list empty>}%
%%  Argument 7: {<e_k><e_(k+1)>{<e_(k+2)>}..<e_n>}%<- this holds the
%%              list of elements/undelimited arguments
\newcommand\UD@MoveElementFromList[7]{%
  \UD@CheckWhetherNull{#1}{%
    \UD@CheckWhetherNull{#7}{%
      \expandafter\UD@Exchange\expandafter{\romannumeral\UD@Expandtimes#6}{#2}%
    }{%
      \UD@CheckWhetherLeadingSpace{#7}{%
        \expandafter\UD@PassFirstToSecond
        \expandafter{\UD@removespace#7}{%
          \expandafter\expandafter\expandafter\UD@PassFirstToSecond
          \expandafter\expandafter\expandafter{%
            \expandafter\UD@Exchange
            \expandafter{\romannumeral\UD@Expandtimes#4{ }}{#2}%
          }{\UD@MoveElementFromList{}}{}{#4}{#5}{#6}%
        }%
      }{%
        \UD@CheckWhetherBrace{#7}{%
          \expandafter\expandafter\expandafter\UD@MoveElementFromList
          \expandafter\UD@PassFirstToSecond
          \expandafter{\UD@firstoftwo{}#7}%
          {{#7\UD@SeLDoM}{#2}{.}{#4}{#5}{#6}}%
        }{%
          \expandafter\expandafter\expandafter\UD@MoveElementFromList
          \expandafter\UD@PassFirstToSecond
          \expandafter{\UD@firstoftwo{}#7}%
          {{#7\UD@SeLDoM}{#2}{}{#4}{#5}{#6}}%
        }%
      }%
    }%
  }{%
    \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
    {%
      \UD@CheckWhetherNull{#3}{%
        \expandafter\expandafter\expandafter\UD@PassFirstToSecond
        \expandafter\expandafter\expandafter{%
          \expandafter\UD@Exchange
          \expandafter{\romannumeral\UD@Expandtimes#4#1}{#2}}%
       }{%
        \expandafter\expandafter\expandafter\UD@PassFirstToSecond
        \expandafter\expandafter\expandafter{%
          \csname UD@\ifnum1>\expandafter\UD@firstoftwo
                             \UD@KeepOnlyFirstBeforeSeLDoM#5\UD@SeLDoM{} %
                            Exchange%
                     \else PassFirstToSecond\fi\expandafter\endcsname
          \expandafter{\romannumeral\expandafter\UD@Expandtimes
                                    \UD@firstoftwo{}#5#1}{#2}}%
      }%
      {\UD@MoveElementFromList{}}{}%
    }%
    {\expandafter\UD@MoveElementFromList
     \expandafter{\UD@KeepOnlyFirstBeforeSeLDoM#1}{#2}{#3}%
    }{#4}{#5}{#6}{#7}%
  }%
}%
%%
%%-----------------------------------------------------------------------------
%% Fork depending on some characters:
%%.............................................................................
\newcommand\UD@@ForkCharacters{}%
\long\def\UD@@ForkCharacters#1ABCDEFGHIJKLMNOPQRSTUVWXYZ#2#3AAA{#2}%
%
\newcommand\UD@ForkCharacter[1]{%
  \UD@@ForkCharacters
  #1BCDEFGHIJKLMNOPQRSTUVWXYZ{a}%
  A#1CDEFGHIJKLMNOPQRSTUVWXYZ{b}%
  AB#1DEFGHIJKLMNOPQRSTUVWXYZ{c}%
  ABC#1EFGHIJKLMNOPQRSTUVWXYZ{d}%
  ABCD#1FGHIJKLMNOPQRSTUVWXYZ{e}%
  ABCDE#1GHIJKLMNOPQRSTUVWXYZ{f}%
  ABCDEF#1HIJKLMNOPQRSTUVWXYZ{g}%
  ABCDEFG#1IJKLMNOPQRSTUVWXYZ{h}%
  ABCDEFGH#1JKLMNOPQRSTUVWXYZ{i}%
  ABCDEFGHI#1KLMNOPQRSTUVWXYZ{j}%
  ABCDEFGHIJ#1LMNOPQRSTUVWXYZ{k}%
  ABCDEFGHIJK#1MNOPQRSTUVWXYZ{l}%
  ABCDEFGHIJKL#1NOPQRSTUVWXYZ{m}%
  ABCDEFGHIJKLM#1OPQRSTUVWXYZ{n}%
  ABCDEFGHIJKLMN#1PQRSTUVWXYZ{o}%
  ABCDEFGHIJKLMNO#1QRSTUVWXYZ{p}%
  ABCDEFGHIJKLMNOP#1RSTUVWXYZ{q}%
  ABCDEFGHIJKLMNOPQ#1STUVWXYZ{r}%
  ABCDEFGHIJKLMNOPQR#1TUVWXYZ{s}%
  ABCDEFGHIJKLMNOPQRS#1UVWXYZ{t}%
  ABCDEFGHIJKLMNOPQRST#1VWXYZ{u}%
  ABCDEFGHIJKLMNOPQRSTU#1WXYZ{v}%
  ABCDEFGHIJKLMNOPQRSTUV#1XYZ{w}%
  ABCDEFGHIJKLMNOPQRSTUVW#1YZ{x}%
  ABCDEFGHIJKLMNOPQRSTUVWX#1Z{y}%
  ABCDEFGHIJKLMNOPQRSTUVWXY#1{z}%
  ABCDEFGHIJKLMNOPQRSTUVWXYZ{#1}%
  AAA%
}%
%%-----------------------------------------------------------------------------
%% Apply the \UD@DoWithEachElementOfArgumentList-for-loop to \UD@ForkCharacter
%%.............................................................................
\newcommand\UD@ExpandableLowercase{%
  \romannumeral
  \expandafter\UD@secondoftwo
  \UD@DoWithEachElementOfArgumentList{{2}\UD@ForkCharacter}%
                                     {{1}{2}\UD@ExpandableLowercase}%
                                     {{0}}%
}%

\begin{document}
\ttfamily\footnotesize

In order to show that \string\UD@ExpandableLowercase{} delivers the 
result after two expansion steps have TeX hit the sequence\\[\medskipamount]%
\string\UD@ExpandableLowercase\string{ABC DEF \string{GHI%
\string{J K LM\string{NOP QR\string}\string#1STUV\string}WXYZ\string}%
ABCDEFGHIJKLMNOPQRSTUVWXYZ\string#1\string}\\[\medskipamount]%
by two \string\expandafter-chains reaching into the definition-text
of the macro \string\test:%

\medskip

\expandafter\expandafter
\expandafter            \def
\expandafter\expandafter
\expandafter            \test
\expandafter\expandafter
\expandafter            #%
\expandafter\expandafter
\expandafter            1%
\expandafter\expandafter
\expandafter           {%
  \UD@ExpandableLowercase{ABC DEF {GHI{J K LM{NOP QR}#1STUV}WXYZ}ABCDEFGHIJKLMNOPQRSTUVWXYZ#1}%
}%

\noindent[\string\test=\meaning\test]

\end{document}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .