6

I would like to define the character @ as follows:

\def @#1#2{\catcode`#1=13\def #1{#2}}

so that when I type @xy x, TeX typesets y. However, when I run this, I get an error.

I have seen other answers here and here that ask about changing catcodes in commands. However, none of the answers given solve the problem in a way that I would like -- they simply move the \catcode out of the macro. There is also the solution

\catcode`@=13
\def @#1{\catcode`#1=13\def}
@xx{y} x

which yields the same result y. However, for my own purposes, I would like to achieve the above goal in the least amount of characters (3): @xy. I attempted to use \expandafter, to no avail. Is this possible?

2
  • With \def@#1#2{...} and @xy x, when @ absorbs x as its first argument, the catcode of x is still letter, not active. Then TeX complains at \def x{y}. May 22 at 4:51
  • Do you wish to use @ for redefining active curly opening brace or active curly closing brace or active hash or active space as well? (If so you need some trickery for stringifying subsequent tokens and cranking out a space...) May 23 at 10:43

3 Answers 3

8

You must to use \lowercase trick:

\def @#1#2{\catcode`#1=13 \bgroup \lccode`\~=`#1\lowercase{\egroup \def~}{#2}}

% test:
@ABA

The ~ is active by default in all formats and \lowercase changes the active ~ to an active character what you want.

1
6

wipet's answer is the way to get the syntax you suggest but if you change to use a doubled character, so @xxy ... x then you can avoid issues with accessing the character being defined before it is active, so use a simpler definition. This is the scheme used in xii.tex which uses A rather than @ but for example AZZFLaLP is equivalent to your suggested @Z{LaL} but with Z that is being defined doubled. @ZZ{LaL}

\let~\catcode~`76~`A13~`F1~`j00~`P2jdefA71F~`7113jdefPALLF
PA''FwPA;;FPAZZFLaLPA//71F71iPAHHFLPAzzFenPASSFthP;A$$FevP
A@@FfPARR717273F737271P;ADDFRgniPAWW71FPATTFvePA**FstRsamP
AGGFRruoPAqq71.72.F717271PAYY7172F727171PA??Fi*LmPA&&71jfi
Fjfi71PAVVFjbigskipRPWGAUU71727374 75,76Fjpar71727375Djifx
:76jelse&U76jfiPLAKK7172F71l7271PAXX71FVLnOSeL71SLRyadR@oL
RrhC?yLRurtKFeLPFovPgaTLtReRomL;PABB71 72,73:Fjif.73.jelse
B73:jfiXF71PU71 72,73:PWs;AMM71F71diPAJJFRdriPAQQFRsreLPAI
I71Fo71dPA!!FRgiePBt'el@ lTLqdrYmu.Q.,Ke;vz vzLqpip.Q.,tz;
;Lql.IrsZ.eap,qn.i. i.eLlMaesLdRcna,;!;h htLqm.MRasZ.ilk,%
s$;z zLqs'.ansZ.Ymi,/sx ;LYegseZRyal,@i;@ TLRlogdLrDsW,@;G
LcYlaDLbJsW,SWXJW ree @rzchLhzsW,;WERcesInW qt.'oL.Rtrul;e
doTsW,Wk;Rri@stW aHAHHFndZPpqar.tridgeLinZpe.LtYer.W,:jbye

Or, if you prefer \ as escape character rather than j

\catcode`\@13
\def\dodef#1#2{\def#1{#2}}
\def@#1{\catcode`#1=13 \dodef}

@xxy   abc x

@ww{hello}  hmmm what

\bye

enter image description here

\dodefhere is just so the braces are optional and you can use @xxy instead of @xx{y}. The xii version uses \def directly so you would need @xx{y} but you can use arguments so that for example @xx#1{(#1)} xq would expand to (q)

2
  • 1
    Hey! I was half expecting Krampus to emerge! May 22 at 14:31
  • 1
    @StevenB.Segletes be good or it may emerge when you least expect May 22 at 18:53
4

The code you show assumes that @ is active to begin with, so a declaration \catcode`@=\active is needed somewhere.

What happens with your

\def @#1#2{\catcode`#1=13\def #1{#2}}
@xy x

attempt? That TeX complains about a missing control sequence, because there is no control sequence or active character in the replacement text of @ and x is tokenized with its category code before the \catcode assignment is performed.

You can use the \lowercase trick as shown by wipet, or a different approach with expl3 (that can be used with plain TeX, but only with e-TeX extensions on).

\input expl3-generic

\ExplSyntaxOn

% first a generic function
\cs_new_protected:Nn \bshepard_catcode_def:nnn
 {% #1 = character to be made active
  % #2 = argument list, may be empty
  % #3 = replacement text
  \cs_new:cn { __bshepard_catcode_def_#1:#2 } { #3 }
  \char_set_active_eq:nc { `#1 } { __bshepard_catcode_def_#1:#2 }
  \char_set_catcode_active:n { `#1 }
 }

%initialize @
\bshepard_catcode_def:nnn { @ } { nn } { \bshepard_catcode_def:nnn { #1 } { } { #2 } }

\ExplSyntaxOff

@xy x

\show x

\bye

The console will show

> x=\long macro:
->y.
l.22 \show x

What's happening? The generic function takes three arguments: a character to be made active, the specification for the arguments its active value will look for and the replacement text.

This will define an internal function based on the character and set the active character meaning equivalent to this internal function, then activates the character.

The line

\bshepard_catcode_def:nnn { @ } { nn } { \bshepard_catcode_def:nnn { #1 } { } { #2 } }

activates @ and tell TeX that its meaning is to look for two arguments (the first of which must be a character) and applies the generic function to this character.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.