Different behavior if no arguments
This is similar, but not a duplicate of Special behavior if optional argument is not passed and Different macro behavior when supplied an argument.
I want to define a macro XXX
such that XXX{1}
and XXX1
behave the same (as they would if I used newcommand{XXX}[1]{...}
to define the macro), but such that XXX
by itself would behave differently. In order words, something like
newcommand{XXX}[1]{Hello #1}
newcommand{XXX}{Bye}
so that XXX1
and XXX2
produce "Hello 1" and "Hello 2" respectively, but XXX
produces "Bye".
The problem with @ifnextchar
is that it does not test for spaces, and I cannot test just for braces. I do not see a way to achieve this with xparse
package either.
Clarification: I do not care how XXX 1
behaves.
Added: After reading the comments and additional reflection, I decided that my intended syntax was not good. Thanks for all the help.
macros optional-arguments parameters
|
show 2 more comments
This is similar, but not a duplicate of Special behavior if optional argument is not passed and Different macro behavior when supplied an argument.
I want to define a macro XXX
such that XXX{1}
and XXX1
behave the same (as they would if I used newcommand{XXX}[1]{...}
to define the macro), but such that XXX
by itself would behave differently. In order words, something like
newcommand{XXX}[1]{Hello #1}
newcommand{XXX}{Bye}
so that XXX1
and XXX2
produce "Hello 1" and "Hello 2" respectively, but XXX
produces "Bye".
The problem with @ifnextchar
is that it does not test for spaces, and I cannot test just for braces. I do not see a way to achieve this with xparse
package either.
Clarification: I do not care how XXX 1
behaves.
Added: After reading the comments and additional reflection, I decided that my intended syntax was not good. Thanks for all the help.
macros optional-arguments parameters
If you could agree on using[1]
and[2]
instead of1
and2
, you could dodocumentclass{article} begin{document} newcommand{XXX}[1][empty]{ifx#1empty Bye else Hello #1 fi} XXX[1] XXX end{document}
.
– marmot
Feb 11 at 22:29
Can you show how you would useXXX
in general? If you want the "mandatory" argument to be conditional, then there might be instances where you're forced to introduce some weird syntax so as to not grab content. For example, willXXX
only accept numbers, or characters as well? If numbers, single or double digits or higher order digits? Will you use input likeXXX1
,XXX{1}
andXXX 1
interchangeably?
– Werner
Feb 11 at 22:39
1
@marmot oh sorry I just noticed your comment, which has some similarities to my answer:-)
– David Carlisle
Feb 11 at 22:57
1
what do you mean byXXX
by itself?XXX1
is the same asXXX 1
so what input followingXXX
would you consider as terminating the construct with no argument?
– David Carlisle
Feb 11 at 23:01
@marmot I can't guess what you mean aboutelse
, but tryXXX[11]
...
– David Carlisle
Feb 11 at 23:06
|
show 2 more comments
This is similar, but not a duplicate of Special behavior if optional argument is not passed and Different macro behavior when supplied an argument.
I want to define a macro XXX
such that XXX{1}
and XXX1
behave the same (as they would if I used newcommand{XXX}[1]{...}
to define the macro), but such that XXX
by itself would behave differently. In order words, something like
newcommand{XXX}[1]{Hello #1}
newcommand{XXX}{Bye}
so that XXX1
and XXX2
produce "Hello 1" and "Hello 2" respectively, but XXX
produces "Bye".
The problem with @ifnextchar
is that it does not test for spaces, and I cannot test just for braces. I do not see a way to achieve this with xparse
package either.
Clarification: I do not care how XXX 1
behaves.
Added: After reading the comments and additional reflection, I decided that my intended syntax was not good. Thanks for all the help.
macros optional-arguments parameters
This is similar, but not a duplicate of Special behavior if optional argument is not passed and Different macro behavior when supplied an argument.
I want to define a macro XXX
such that XXX{1}
and XXX1
behave the same (as they would if I used newcommand{XXX}[1]{...}
to define the macro), but such that XXX
by itself would behave differently. In order words, something like
newcommand{XXX}[1]{Hello #1}
newcommand{XXX}{Bye}
so that XXX1
and XXX2
produce "Hello 1" and "Hello 2" respectively, but XXX
produces "Bye".
The problem with @ifnextchar
is that it does not test for spaces, and I cannot test just for braces. I do not see a way to achieve this with xparse
package either.
Clarification: I do not care how XXX 1
behaves.
Added: After reading the comments and additional reflection, I decided that my intended syntax was not good. Thanks for all the help.
macros optional-arguments parameters
macros optional-arguments parameters
edited Feb 12 at 1:47
Boris Bukh
asked Feb 11 at 22:20
Boris BukhBoris Bukh
8151921
8151921
If you could agree on using[1]
and[2]
instead of1
and2
, you could dodocumentclass{article} begin{document} newcommand{XXX}[1][empty]{ifx#1empty Bye else Hello #1 fi} XXX[1] XXX end{document}
.
– marmot
Feb 11 at 22:29
Can you show how you would useXXX
in general? If you want the "mandatory" argument to be conditional, then there might be instances where you're forced to introduce some weird syntax so as to not grab content. For example, willXXX
only accept numbers, or characters as well? If numbers, single or double digits or higher order digits? Will you use input likeXXX1
,XXX{1}
andXXX 1
interchangeably?
– Werner
Feb 11 at 22:39
1
@marmot oh sorry I just noticed your comment, which has some similarities to my answer:-)
– David Carlisle
Feb 11 at 22:57
1
what do you mean byXXX
by itself?XXX1
is the same asXXX 1
so what input followingXXX
would you consider as terminating the construct with no argument?
– David Carlisle
Feb 11 at 23:01
@marmot I can't guess what you mean aboutelse
, but tryXXX[11]
...
– David Carlisle
Feb 11 at 23:06
|
show 2 more comments
If you could agree on using[1]
and[2]
instead of1
and2
, you could dodocumentclass{article} begin{document} newcommand{XXX}[1][empty]{ifx#1empty Bye else Hello #1 fi} XXX[1] XXX end{document}
.
– marmot
Feb 11 at 22:29
Can you show how you would useXXX
in general? If you want the "mandatory" argument to be conditional, then there might be instances where you're forced to introduce some weird syntax so as to not grab content. For example, willXXX
only accept numbers, or characters as well? If numbers, single or double digits or higher order digits? Will you use input likeXXX1
,XXX{1}
andXXX 1
interchangeably?
– Werner
Feb 11 at 22:39
1
@marmot oh sorry I just noticed your comment, which has some similarities to my answer:-)
– David Carlisle
Feb 11 at 22:57
1
what do you mean byXXX
by itself?XXX1
is the same asXXX 1
so what input followingXXX
would you consider as terminating the construct with no argument?
– David Carlisle
Feb 11 at 23:01
@marmot I can't guess what you mean aboutelse
, but tryXXX[11]
...
– David Carlisle
Feb 11 at 23:06
If you could agree on using
[1]
and [2]
instead of 1
and 2
, you could do documentclass{article} begin{document} newcommand{XXX}[1][empty]{ifx#1empty Bye else Hello #1 fi} XXX[1] XXX end{document}
.– marmot
Feb 11 at 22:29
If you could agree on using
[1]
and [2]
instead of 1
and 2
, you could do documentclass{article} begin{document} newcommand{XXX}[1][empty]{ifx#1empty Bye else Hello #1 fi} XXX[1] XXX end{document}
.– marmot
Feb 11 at 22:29
Can you show how you would use
XXX
in general? If you want the "mandatory" argument to be conditional, then there might be instances where you're forced to introduce some weird syntax so as to not grab content. For example, will XXX
only accept numbers, or characters as well? If numbers, single or double digits or higher order digits? Will you use input like XXX1
, XXX{1}
and XXX 1
interchangeably?– Werner
Feb 11 at 22:39
Can you show how you would use
XXX
in general? If you want the "mandatory" argument to be conditional, then there might be instances where you're forced to introduce some weird syntax so as to not grab content. For example, will XXX
only accept numbers, or characters as well? If numbers, single or double digits or higher order digits? Will you use input like XXX1
, XXX{1}
and XXX 1
interchangeably?– Werner
Feb 11 at 22:39
1
1
@marmot oh sorry I just noticed your comment, which has some similarities to my answer:-)
– David Carlisle
Feb 11 at 22:57
@marmot oh sorry I just noticed your comment, which has some similarities to my answer:-)
– David Carlisle
Feb 11 at 22:57
1
1
what do you mean by
XXX
by itself? XXX1
is the same as XXX 1
so what input following XXX
would you consider as terminating the construct with no argument?– David Carlisle
Feb 11 at 23:01
what do you mean by
XXX
by itself? XXX1
is the same as XXX 1
so what input following XXX
would you consider as terminating the construct with no argument?– David Carlisle
Feb 11 at 23:01
@marmot I can't guess what you mean about
else
, but try XXX[11]
...– David Carlisle
Feb 11 at 23:06
@marmot I can't guess what you mean about
else
, but try XXX[11]
...– David Carlisle
Feb 11 at 23:06
|
show 2 more comments
3 Answers
3
active
oldest
votes
There will be no space tokens after XXX
so you could use @ifnextcharlbrace
(or you could use xparse g
argument type) But either are totally against latex syntax. The argument is optional so should use [..]
not {..}
.
A syntax that follows LaTeX syntax conventions would be
newcommandXXX[1][relax]{ifxrelax#1 bye else hello #1fi}
XXX
XXX[1]
1
note that if you make an optional argument use{}
delimiters then you need to make the{}
mandatory, you can't really have that and haveXXX 1
being the same asXXX {1}
as they are conflicting requirements.
– David Carlisle
Feb 11 at 23:03
add a comment |
documentclass{article}
makeatletter
% If #1 is empty or does hold space-tokens only, yields "bye",
% otherwise yields "hello #1".
% Requires an engine with eTeX-extensions.
newcommandXXX[1]{%
ifrelaxdetokenizeexpandafter{@firstofone#1{}}relax bye else hello #1fi
}
makeatother
begin{document}
XXX{}
XXX{ }
XXX{1}
end{document}
add a comment |
You can, e.g., create a command which has LaTeX temporarily change the catcode of space, tab, return and % (% is used for commenting) and via a variant of kernel@ifnextchar
(which itself is based on futurelet
) check the next token in the token-stream.
Some days ago, in my answer to the question Space after LaTeX commands, I tried to explain the drawbacks of the approach of having LaTeX "look ahead" at the next token:
The major drawback of this method is that it relies on the next token in the token-stream coming into being by reading and tokenizing tex-input from the tex-source-code while the temporary changes of these catcodes are effective.
But tokens can also get into the token-stream not by reading and tokenizing tex-input from the tex-source-code but as a result of expanding, e.g., a macro-token where both the replacement-text and the arguments got tokenized at points in time when these category-codes were not changed.
Commands that temporarily change the category-code-régime and rely on the changed category-code-régime being in effect when tokens which they shall process get tokenized cannot be used in circumstances where the things they shall process will already have been tokenized under the unchanged category-code-régime as would be the case, e.g., when they get their arguments passed by other macros as a result of expanding these other macros.
Therefore with the example below, XXX
is defined in terms of outer
for ensuring as good as possible that it will not be used within the definition-texts or the arguments of other macros.
You need another variant of kernel@ifnextchar
because you cannot safely use kernel@ifnextchar
as kernel@ifnextchar
is definitely not 100%ly reliable:
The commented sources of LaTeX 2e as a pdf-file whose name is source2e.pdf can be found at http://mirrors.ctan.org/macros/latex/base/source2e.pdf.
kernel@ifnextchar
in the LaTeX 2e sources is defined as follows—File d: ltdefns.dtx Date: 2018/09/26 Version v1.5e :
321 longdef@ifnextchar#1#2#3{%
322 letreserved@d=#1%
323 defreserved@a{#2}%
324 defreserved@b{#3}%
325 futurelet@let@token@ifnch}
326 letkernel@ifnextchar@ifnextchar
327 def@ifnch{%
328 ifx@let@token@sptoken
329 letreserved@c@xifnch
330 else
331 ifx@let@tokenreserved@d
332 letreserved@creserved@a
333 else
334 letreserved@creserved@b
335 fi
336 fi
337 reserved@c}
338 def:{let@sptoken= } : % this makes @sptoken a space token
339 def:{@xifnch} expandafterdef: {futurelet@let@token@ifnch}
E.g., you said you cannot test for a space. That's true. There was extra effort for implementing a loop that does remove spaces when implementing
kernel@ifnextchar
/@ifnextchar
.
This loop leads to error-messages with things like:
kernel@ifnextchar{⟨char⟩}{The next thing is ⟨char⟩}{The next thing is not ⟨char⟩}@sptoken
That has to do with the fact that knowing whether tokens have the same meaning does not imply knowing whether they are the same tokens.
When the token trailing the arguments of
kernel@ifnextchar
has the meaning of the tokenreserved@d
, i.e., when you have something like
kernel@ifnextchar{⟨char⟩}{The next thing is ⟨char⟩}{The next thing is not ⟨char⟩}reserved@d
, you get
kernel@ifnextchar
's second argument no matter what its first argument is.
(That's why things like
reserved@d
are reserved. ;-) )
See, e.g., what you get from
documentclass{article}
makeatletter
begin{document}
% This is nice:
kernel@ifnextchar{X}{The next thing is X}{The next thing is not X}reserved@d
kernel@ifnextchar{Y}{The next thing is Y}{The next thing is not Y}reserved@d
kernel@ifnextchar{Z}{The next thing is Z}{The next thing is not Z}reserved@d
kernel@ifnextchar{LaTeX}{The next thing is LaTeX}{The next thing is not LaTeX}reserved@d
% This raises nice errors.
% kernel@ifnextchar{X}{The next thing is X}{The next thing is not X}@sptoken X
end{document}
The reason for this is that
kernel@ifnextchar
does not distinguish different tokens from each other which have the same meaning. As a special case of this behavior,kernel@ifnextchar
does not distinguish implicit characters from explicit characters.
See, e.g., what you get from
documentclass{article}
makeatletter
letimplicitA=A
begin{document}
kernel@ifnextchar{A}{We have an }{We don't have an }implicitA
kernel@ifnextchar{implicitA}{We have an }{We don't have an }A
end{document}
Above it was said:
You can, e.g., create a command which has LaTeX temporarily change the catcode of space, tab, return and % (% is used for commenting) and via a variant of
kernel@ifnextchar
(which itself is based onfuturelet
) check the next token in the token-stream.
If the catcode of these characters is switched to 12(other) and checking only for these characters is of interest, you can have LaTeX peek at the meaning of the next token via futurelet
while leaving that token in place.
In case the meaning of the next token equals the meaning of one of these explicit catcode-12-character-tokens, you can safely have LaTeX grab that token as an undelimited macro argument. Thus in this special case of checking for explicit catcode-12-characters you can have LaTeX "grab" the token itself for defining a temporary macro that expands to that token. If there also is already defined another temporary argument that expands to your kernel@ifnextchar
-variant's first argument, LaTeX can do an ifx
-comparison with these temporary macros for making sure that the two tokens in question do not just have the same meanings but are really the same tokens. (When grabbing the next token as argument, LaTeX should probably put it back in the right moment...)
In the example below I tried to implement a routine UD@ifnextcharForOtherTokens
which causes LaTeX to do these things. At least I hope it does. ;-)
documentclass{article}
makeatletter
% Patch verbatim to also display horizontal tabs:
% The patch is needed for this example only.
usepackage{amssymb}
g@addto@macrodospecials{keystroketab}
newboxUD@tempbox
begingroup
catcode`^^I=13relax
@firstofone{%
endgroup
newcommandkeystroketab{%
setboxUD@tempboxhbox{verbatim@fontchar32}%
catcode`^^I=13relax
def^^I{mbox{hbox to 3wdUD@tempbox{nullhfill$leftrightarrows$hfillnull}}}%
}%
}%
%verbatim-patch done.
% UD@ifnextcharForOtherTokens peeks at the following token and
% compares it with its first argument w h i c h m u s t b e
% a s i n g l e t o k e n a n d w h i c h i n c a s e
% o f b e i n g a c h a r a c t er t o k e n --- b e
% i t e x p l i c i t o r i m p l i c i t --- m u s t
% b e a c h a r a c t e r t o k e n w h o s e e x p l i c i t
% v a r i a n t c a n b e s a f e l y g r a b b e d a s
% u n d e l i m i t e d a r g u m e n t!!! T h i s i s t h e
% c a s e, e. g., w i t h c h a r a c t er t o k e n s o f
% c a t e g o r y c o d e 12(other)!
% If both are the same it executes its second argument, otherwise
% its third.
newcommandUD@reserved@a{}%
newcommandUD@reserved@b{}%
newcommandUD@reserved@c{}%
newcommandUD@reserved@d{}%
newcommandUD@let@token{}%
newcommandUD@ifnextcharForOtherTokens[3]{%
begingroup
defUD@reserved@d{#1}%
defUD@reserved@a{#2}%
defUD@reserved@b{#3}%
futureletUD@let@tokenUD@ifnch
}%
newcommandUD@ifnch{%
expandafterifxexpandafterUD@let@tokenUD@reserved@d
expandafterUD@ifnchsnapnexttoken
else
expandafterexpandafterexpandafter
endgroupexpandafterUD@reserved@b
fi
}%
newcommandUD@ifnchsnapnexttoken[1]{%
defUD@reserved@c{#1}%
ifxUD@reserved@cUD@reserved@d
expandafterexpandafterexpandafter
endgroupexpandafterUD@reserved@a
else
expandafterexpandafterexpandafter
endgroupexpandafterUD@reserved@b
fi
#1%
}%
begingroup
% -------------------------------------------------
% Don't indent the following code!
% Each line of the following code must end with ^^A
% vwhich serves as comment-char!
catcode`^^A=14relax%
catcode` =12relax^^A
catcode`^^I=12relax^^A
catcode`^^M=12relax^^A
catcode`%=12relax^^A
@firstofone{^^A
endgroup^^A
newcommandUD@removeotherspace{}^^A
longdefUD@removeotherspace#1 {#1}^^A
newcommandUD@removeotherreturn{}^^A
longdefUD@removeotherreturn#1^^M{#1}^^A
newcommandUD@removeothertab{}^^A
longdefUD@removeothertab#1^^I{#1}^^A
newcommandUD@removecomment{}^^A
longdefUD@removecomment#1#2%#3^^M{endgroupUD@removecommentloop{#1}{#2}}^^A
begingroup^^A
newcommandUD@CheckWhetherSourceSpace[1]{^^A
endgroup^^A
newcommandUD@removecommentloop[2]{^^A
UD@ifnextcharForOtherTokens{ }{UD@removeotherspace{UD@removecommentloop{##1}{##2}}}{^^A
UD@ifnextcharForOtherTokens{^^I}{UD@removeothertab{UD@removecommentloop{##1}{##2}}}{^^A
UD@ifnextcharForOtherTokens{%}{begingroupcatcode`^=12#1UD@removecomment{##1}{##2}}{^^A
UD@ifnextcharForOtherTokens{^^M}{UD@removeotherreturn{endgroup##1{par}}}{endgroup##2}^^A
}}}}^^A
newcommandUD@CheckWhetherSourceSpace[2]{^^A
begingroup^^A
catcode` =12relax^^A
catcode`^^I=12relax^^A
catcode`^^M=12relax^^A
catcode`%=12relax^^A
UD@ifnextcharForOtherTokens{%}{begingroupcatcode`^=12#1UD@removecomment{##1}{##2}}{^^A
UD@ifnextcharForOtherTokens{ }{endgroupUD@removeotherspace{##1{#1ignorespaces}}}{^^A
UD@ifnextcharForOtherTokens{^^M}{endgroupUD@removeotherreturn{##1{#1ignorespaces}}}{^^A
UD@ifnextcharForOtherTokens{^^I}{endgroupUD@removeothertab{##1{#1ignorespaces}}}{endgroup##2}^^A
}}}}}^^A
}%<- closing brace of @firstofone's argument
UD@CheckWhetherSourceSpace{ }%
%
% Now we are back to normal circumstances. ;-)
% -------------------------------------------------
%
% !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
% Commands that call UD@CheckWhetherSourceSpace must be defined in
% terms of outer.
% They may be used only in circumstances where their arguments get
% read and tokenized _while_ they are carried out.
% They must not be used in circumstances where the tokens probably
% forming their arguments already got tokenized and then were passed
% on to them via "spitting out" some macro-definition or
% macro-argument or the like.
% Be aware that defining in terms of outer does not prevent
% erroneous usage to 100%,
% !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
%
% The first argument of UD@CheckWhetherSourceSpace is a macro that
% performs the action in case no argument is present. It seems
% confusing but it must nonetheless process one argument.
% That argument is deliveredby UD@CheckWhetherSourceSpace as a means
% of providing info about whether a space token or a par-token
% needs to be appended after the action.
% The second argument of UD@CheckWhetherSourceSpace is a macro that
% performs the action in case an argument is present.
newcommandXXX{}
outerdefXXX{%
UD@CheckWhetherSourceSpace{XXX@Space}{XXX@Arg}%
}%
newcommandXXX@Space[1]{fbox{Bye}#1}%
newcommandXXX@Arg[1]{fbox{Hello #1}}
makeatother
parindent=-.66cm
begin{document}
{bfseries Linebreak between verb|XXX| and following stuff:}
emph{The code}
begingrouptopsep=0expartopsep=0ex
begin{verbatim*}
text XXX
1 A
end{verbatim*}%
endgroup
emph{yields:}\
text XXX
1 A
emph{The code}
begingrouptopsep=0expartopsep=0ex
begin{verbatim*}
text XXX
{1} A
end{verbatim*}
endgroup
emph{yields:}\
text XXX
{1} A
vfill
{bfseries Comment and linebreak between verb|XXX| and following stuff:}
emph{The code}
begingrouptopsep=0expartopsep=0ex
begin{verbatim*}
text XXX%
% Comment ^^M^^M Comment
% Comment
1 A
end{verbatim*}
endgroup
emph{yields:}\
text XXX%
% Comment ^^M^^M Comment
% Comment
1 A
vfill
{bfseries Comment and linebreak and linebreak between verb|XXX| and following stuff:}
emph{The code}
begingrouptopsep=0expartopsep=0ex
begin{verbatim*}
text XXX%
% Comment ^^M^^M Comment
% Comment
1 A
end{verbatim*}
endgroup
emph{yields:}\
begingroupparindent=0ex
text XXX%
% Comment ^^M^^M Comment
% Comment
1 A
endgroup
vfill
{bfseries Space between verb|XXX| and following stuff:}
verb*|text XXX 1 A|: text XXX 1 A
verb*|text XXX {1} A|: text XXX {1} A
vfill
{bfseries Horizontal tab between verb|XXX| and following stuff:}
verb*|text XXX 1 A|: text XXX 1 A
verb*|text XXX {1} A|: text XXX {1} A
vfill
{bfseries Nothing between verb|XXX| and following stuff:}
verb*|text XXX1 A|: text XXX1 A
verb*|text XXX{1} A|: text XXX{1} A
verb*|text XXX123 A|: text XXX123 A
verb*|text XXX{123} A|: text XXX{123} A
vfillvfillvfillvfillvfillvfill
end{document}
For some reason unknown to me horizontal-tabs in examples get transformed into four spaces when pasting code into the "Answer"-window of StackExchange.
Thus the horizontal-tabs I typed into the example before compiling it may during the copy-paste-process have been transformed to spaces which implies that the result of copy-pasting the example from StackExchange and compiling may have a look which is slightly different from the look of the image above.
I state clearly and explicitly that I do not recommend this approach. It has too many drawbacks and restrictions. The example is intended to show how confusing (La)TeX-programming can be. ;-)
One restriction is already mentioned: You can't use XXX
within macro-definitions or within macro-arguments. You'd better not have other macros deliver XXX
's argument or tokens that shall be behind XXX
but not be taken for XXX
's argument.
Some other restrictions and drawbacks are:
XXX
performs a lot of temporary assignments. ThusXXX
is not fully expandable and cannot be used safely in pure-expansion-contexts.
A thing like
XXX
could not be used safely within moving-arguments, i.e., within things that, e.g., get written to temporary files so that in future LaTeX-runs they can pop up in the table of contents or withinlabel
-ref
-cross-references also. One of the reasons for this is that LaTeX does always attach a space character when unexpanded-writing a control-word-token to an external text-file.
E.g.,
newwritemywrite
immediateopenoutmywrite experiment.tex %
immediatewritemywrite{noexpandXXX{Something}}%
immediatecloseoutmywrite
yields a file experiment.tex whose content is:
XXX␣{Something}
Note the space character between
XXX
and{Something}
.
Thanks. As I said in the update to my question: I was dissuaded from this approach. However, it is still interesting that this is possible, and see a way to do so.
– Boris Bukh
Feb 12 at 20:45
add a comment |
Your Answer
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "85"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2ftex.stackexchange.com%2fquestions%2f474424%2fdifferent-behavior-if-no-arguments%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
There will be no space tokens after XXX
so you could use @ifnextcharlbrace
(or you could use xparse g
argument type) But either are totally against latex syntax. The argument is optional so should use [..]
not {..}
.
A syntax that follows LaTeX syntax conventions would be
newcommandXXX[1][relax]{ifxrelax#1 bye else hello #1fi}
XXX
XXX[1]
1
note that if you make an optional argument use{}
delimiters then you need to make the{}
mandatory, you can't really have that and haveXXX 1
being the same asXXX {1}
as they are conflicting requirements.
– David Carlisle
Feb 11 at 23:03
add a comment |
There will be no space tokens after XXX
so you could use @ifnextcharlbrace
(or you could use xparse g
argument type) But either are totally against latex syntax. The argument is optional so should use [..]
not {..}
.
A syntax that follows LaTeX syntax conventions would be
newcommandXXX[1][relax]{ifxrelax#1 bye else hello #1fi}
XXX
XXX[1]
1
note that if you make an optional argument use{}
delimiters then you need to make the{}
mandatory, you can't really have that and haveXXX 1
being the same asXXX {1}
as they are conflicting requirements.
– David Carlisle
Feb 11 at 23:03
add a comment |
There will be no space tokens after XXX
so you could use @ifnextcharlbrace
(or you could use xparse g
argument type) But either are totally against latex syntax. The argument is optional so should use [..]
not {..}
.
A syntax that follows LaTeX syntax conventions would be
newcommandXXX[1][relax]{ifxrelax#1 bye else hello #1fi}
XXX
XXX[1]
There will be no space tokens after XXX
so you could use @ifnextcharlbrace
(or you could use xparse g
argument type) But either are totally against latex syntax. The argument is optional so should use [..]
not {..}
.
A syntax that follows LaTeX syntax conventions would be
newcommandXXX[1][relax]{ifxrelax#1 bye else hello #1fi}
XXX
XXX[1]
edited Feb 11 at 23:04
answered Feb 11 at 22:56
David CarlisleDavid Carlisle
491k4111341883
491k4111341883
1
note that if you make an optional argument use{}
delimiters then you need to make the{}
mandatory, you can't really have that and haveXXX 1
being the same asXXX {1}
as they are conflicting requirements.
– David Carlisle
Feb 11 at 23:03
add a comment |
1
note that if you make an optional argument use{}
delimiters then you need to make the{}
mandatory, you can't really have that and haveXXX 1
being the same asXXX {1}
as they are conflicting requirements.
– David Carlisle
Feb 11 at 23:03
1
1
note that if you make an optional argument use
{}
delimiters then you need to make the {}
mandatory, you can't really have that and have XXX 1
being the same as XXX {1}
as they are conflicting requirements.– David Carlisle
Feb 11 at 23:03
note that if you make an optional argument use
{}
delimiters then you need to make the {}
mandatory, you can't really have that and have XXX 1
being the same as XXX {1}
as they are conflicting requirements.– David Carlisle
Feb 11 at 23:03
add a comment |
documentclass{article}
makeatletter
% If #1 is empty or does hold space-tokens only, yields "bye",
% otherwise yields "hello #1".
% Requires an engine with eTeX-extensions.
newcommandXXX[1]{%
ifrelaxdetokenizeexpandafter{@firstofone#1{}}relax bye else hello #1fi
}
makeatother
begin{document}
XXX{}
XXX{ }
XXX{1}
end{document}
add a comment |
documentclass{article}
makeatletter
% If #1 is empty or does hold space-tokens only, yields "bye",
% otherwise yields "hello #1".
% Requires an engine with eTeX-extensions.
newcommandXXX[1]{%
ifrelaxdetokenizeexpandafter{@firstofone#1{}}relax bye else hello #1fi
}
makeatother
begin{document}
XXX{}
XXX{ }
XXX{1}
end{document}
add a comment |
documentclass{article}
makeatletter
% If #1 is empty or does hold space-tokens only, yields "bye",
% otherwise yields "hello #1".
% Requires an engine with eTeX-extensions.
newcommandXXX[1]{%
ifrelaxdetokenizeexpandafter{@firstofone#1{}}relax bye else hello #1fi
}
makeatother
begin{document}
XXX{}
XXX{ }
XXX{1}
end{document}
documentclass{article}
makeatletter
% If #1 is empty or does hold space-tokens only, yields "bye",
% otherwise yields "hello #1".
% Requires an engine with eTeX-extensions.
newcommandXXX[1]{%
ifrelaxdetokenizeexpandafter{@firstofone#1{}}relax bye else hello #1fi
}
makeatother
begin{document}
XXX{}
XXX{ }
XXX{1}
end{document}
edited Feb 12 at 11:08
answered Feb 12 at 2:13
Ulrich DiezUlrich Diez
4,865618
4,865618
add a comment |
add a comment |
You can, e.g., create a command which has LaTeX temporarily change the catcode of space, tab, return and % (% is used for commenting) and via a variant of kernel@ifnextchar
(which itself is based on futurelet
) check the next token in the token-stream.
Some days ago, in my answer to the question Space after LaTeX commands, I tried to explain the drawbacks of the approach of having LaTeX "look ahead" at the next token:
The major drawback of this method is that it relies on the next token in the token-stream coming into being by reading and tokenizing tex-input from the tex-source-code while the temporary changes of these catcodes are effective.
But tokens can also get into the token-stream not by reading and tokenizing tex-input from the tex-source-code but as a result of expanding, e.g., a macro-token where both the replacement-text and the arguments got tokenized at points in time when these category-codes were not changed.
Commands that temporarily change the category-code-régime and rely on the changed category-code-régime being in effect when tokens which they shall process get tokenized cannot be used in circumstances where the things they shall process will already have been tokenized under the unchanged category-code-régime as would be the case, e.g., when they get their arguments passed by other macros as a result of expanding these other macros.
Therefore with the example below, XXX
is defined in terms of outer
for ensuring as good as possible that it will not be used within the definition-texts or the arguments of other macros.
You need another variant of kernel@ifnextchar
because you cannot safely use kernel@ifnextchar
as kernel@ifnextchar
is definitely not 100%ly reliable:
The commented sources of LaTeX 2e as a pdf-file whose name is source2e.pdf can be found at http://mirrors.ctan.org/macros/latex/base/source2e.pdf.
kernel@ifnextchar
in the LaTeX 2e sources is defined as follows—File d: ltdefns.dtx Date: 2018/09/26 Version v1.5e :
321 longdef@ifnextchar#1#2#3{%
322 letreserved@d=#1%
323 defreserved@a{#2}%
324 defreserved@b{#3}%
325 futurelet@let@token@ifnch}
326 letkernel@ifnextchar@ifnextchar
327 def@ifnch{%
328 ifx@let@token@sptoken
329 letreserved@c@xifnch
330 else
331 ifx@let@tokenreserved@d
332 letreserved@creserved@a
333 else
334 letreserved@creserved@b
335 fi
336 fi
337 reserved@c}
338 def:{let@sptoken= } : % this makes @sptoken a space token
339 def:{@xifnch} expandafterdef: {futurelet@let@token@ifnch}
E.g., you said you cannot test for a space. That's true. There was extra effort for implementing a loop that does remove spaces when implementing
kernel@ifnextchar
/@ifnextchar
.
This loop leads to error-messages with things like:
kernel@ifnextchar{⟨char⟩}{The next thing is ⟨char⟩}{The next thing is not ⟨char⟩}@sptoken
That has to do with the fact that knowing whether tokens have the same meaning does not imply knowing whether they are the same tokens.
When the token trailing the arguments of
kernel@ifnextchar
has the meaning of the tokenreserved@d
, i.e., when you have something like
kernel@ifnextchar{⟨char⟩}{The next thing is ⟨char⟩}{The next thing is not ⟨char⟩}reserved@d
, you get
kernel@ifnextchar
's second argument no matter what its first argument is.
(That's why things like
reserved@d
are reserved. ;-) )
See, e.g., what you get from
documentclass{article}
makeatletter
begin{document}
% This is nice:
kernel@ifnextchar{X}{The next thing is X}{The next thing is not X}reserved@d
kernel@ifnextchar{Y}{The next thing is Y}{The next thing is not Y}reserved@d
kernel@ifnextchar{Z}{The next thing is Z}{The next thing is not Z}reserved@d
kernel@ifnextchar{LaTeX}{The next thing is LaTeX}{The next thing is not LaTeX}reserved@d
% This raises nice errors.
% kernel@ifnextchar{X}{The next thing is X}{The next thing is not X}@sptoken X
end{document}
The reason for this is that
kernel@ifnextchar
does not distinguish different tokens from each other which have the same meaning. As a special case of this behavior,kernel@ifnextchar
does not distinguish implicit characters from explicit characters.
See, e.g., what you get from
documentclass{article}
makeatletter
letimplicitA=A
begin{document}
kernel@ifnextchar{A}{We have an }{We don't have an }implicitA
kernel@ifnextchar{implicitA}{We have an }{We don't have an }A
end{document}
Above it was said:
You can, e.g., create a command which has LaTeX temporarily change the catcode of space, tab, return and % (% is used for commenting) and via a variant of
kernel@ifnextchar
(which itself is based onfuturelet
) check the next token in the token-stream.
If the catcode of these characters is switched to 12(other) and checking only for these characters is of interest, you can have LaTeX peek at the meaning of the next token via futurelet
while leaving that token in place.
In case the meaning of the next token equals the meaning of one of these explicit catcode-12-character-tokens, you can safely have LaTeX grab that token as an undelimited macro argument. Thus in this special case of checking for explicit catcode-12-characters you can have LaTeX "grab" the token itself for defining a temporary macro that expands to that token. If there also is already defined another temporary argument that expands to your kernel@ifnextchar
-variant's first argument, LaTeX can do an ifx
-comparison with these temporary macros for making sure that the two tokens in question do not just have the same meanings but are really the same tokens. (When grabbing the next token as argument, LaTeX should probably put it back in the right moment...)
In the example below I tried to implement a routine UD@ifnextcharForOtherTokens
which causes LaTeX to do these things. At least I hope it does. ;-)
documentclass{article}
makeatletter
% Patch verbatim to also display horizontal tabs:
% The patch is needed for this example only.
usepackage{amssymb}
g@addto@macrodospecials{keystroketab}
newboxUD@tempbox
begingroup
catcode`^^I=13relax
@firstofone{%
endgroup
newcommandkeystroketab{%
setboxUD@tempboxhbox{verbatim@fontchar32}%
catcode`^^I=13relax
def^^I{mbox{hbox to 3wdUD@tempbox{nullhfill$leftrightarrows$hfillnull}}}%
}%
}%
%verbatim-patch done.
% UD@ifnextcharForOtherTokens peeks at the following token and
% compares it with its first argument w h i c h m u s t b e
% a s i n g l e t o k e n a n d w h i c h i n c a s e
% o f b e i n g a c h a r a c t er t o k e n --- b e
% i t e x p l i c i t o r i m p l i c i t --- m u s t
% b e a c h a r a c t e r t o k e n w h o s e e x p l i c i t
% v a r i a n t c a n b e s a f e l y g r a b b e d a s
% u n d e l i m i t e d a r g u m e n t!!! T h i s i s t h e
% c a s e, e. g., w i t h c h a r a c t er t o k e n s o f
% c a t e g o r y c o d e 12(other)!
% If both are the same it executes its second argument, otherwise
% its third.
newcommandUD@reserved@a{}%
newcommandUD@reserved@b{}%
newcommandUD@reserved@c{}%
newcommandUD@reserved@d{}%
newcommandUD@let@token{}%
newcommandUD@ifnextcharForOtherTokens[3]{%
begingroup
defUD@reserved@d{#1}%
defUD@reserved@a{#2}%
defUD@reserved@b{#3}%
futureletUD@let@tokenUD@ifnch
}%
newcommandUD@ifnch{%
expandafterifxexpandafterUD@let@tokenUD@reserved@d
expandafterUD@ifnchsnapnexttoken
else
expandafterexpandafterexpandafter
endgroupexpandafterUD@reserved@b
fi
}%
newcommandUD@ifnchsnapnexttoken[1]{%
defUD@reserved@c{#1}%
ifxUD@reserved@cUD@reserved@d
expandafterexpandafterexpandafter
endgroupexpandafterUD@reserved@a
else
expandafterexpandafterexpandafter
endgroupexpandafterUD@reserved@b
fi
#1%
}%
begingroup
% -------------------------------------------------
% Don't indent the following code!
% Each line of the following code must end with ^^A
% vwhich serves as comment-char!
catcode`^^A=14relax%
catcode` =12relax^^A
catcode`^^I=12relax^^A
catcode`^^M=12relax^^A
catcode`%=12relax^^A
@firstofone{^^A
endgroup^^A
newcommandUD@removeotherspace{}^^A
longdefUD@removeotherspace#1 {#1}^^A
newcommandUD@removeotherreturn{}^^A
longdefUD@removeotherreturn#1^^M{#1}^^A
newcommandUD@removeothertab{}^^A
longdefUD@removeothertab#1^^I{#1}^^A
newcommandUD@removecomment{}^^A
longdefUD@removecomment#1#2%#3^^M{endgroupUD@removecommentloop{#1}{#2}}^^A
begingroup^^A
newcommandUD@CheckWhetherSourceSpace[1]{^^A
endgroup^^A
newcommandUD@removecommentloop[2]{^^A
UD@ifnextcharForOtherTokens{ }{UD@removeotherspace{UD@removecommentloop{##1}{##2}}}{^^A
UD@ifnextcharForOtherTokens{^^I}{UD@removeothertab{UD@removecommentloop{##1}{##2}}}{^^A
UD@ifnextcharForOtherTokens{%}{begingroupcatcode`^=12#1UD@removecomment{##1}{##2}}{^^A
UD@ifnextcharForOtherTokens{^^M}{UD@removeotherreturn{endgroup##1{par}}}{endgroup##2}^^A
}}}}^^A
newcommandUD@CheckWhetherSourceSpace[2]{^^A
begingroup^^A
catcode` =12relax^^A
catcode`^^I=12relax^^A
catcode`^^M=12relax^^A
catcode`%=12relax^^A
UD@ifnextcharForOtherTokens{%}{begingroupcatcode`^=12#1UD@removecomment{##1}{##2}}{^^A
UD@ifnextcharForOtherTokens{ }{endgroupUD@removeotherspace{##1{#1ignorespaces}}}{^^A
UD@ifnextcharForOtherTokens{^^M}{endgroupUD@removeotherreturn{##1{#1ignorespaces}}}{^^A
UD@ifnextcharForOtherTokens{^^I}{endgroupUD@removeothertab{##1{#1ignorespaces}}}{endgroup##2}^^A
}}}}}^^A
}%<- closing brace of @firstofone's argument
UD@CheckWhetherSourceSpace{ }%
%
% Now we are back to normal circumstances. ;-)
% -------------------------------------------------
%
% !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
% Commands that call UD@CheckWhetherSourceSpace must be defined in
% terms of outer.
% They may be used only in circumstances where their arguments get
% read and tokenized _while_ they are carried out.
% They must not be used in circumstances where the tokens probably
% forming their arguments already got tokenized and then were passed
% on to them via "spitting out" some macro-definition or
% macro-argument or the like.
% Be aware that defining in terms of outer does not prevent
% erroneous usage to 100%,
% !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
%
% The first argument of UD@CheckWhetherSourceSpace is a macro that
% performs the action in case no argument is present. It seems
% confusing but it must nonetheless process one argument.
% That argument is deliveredby UD@CheckWhetherSourceSpace as a means
% of providing info about whether a space token or a par-token
% needs to be appended after the action.
% The second argument of UD@CheckWhetherSourceSpace is a macro that
% performs the action in case an argument is present.
newcommandXXX{}
outerdefXXX{%
UD@CheckWhetherSourceSpace{XXX@Space}{XXX@Arg}%
}%
newcommandXXX@Space[1]{fbox{Bye}#1}%
newcommandXXX@Arg[1]{fbox{Hello #1}}
makeatother
parindent=-.66cm
begin{document}
{bfseries Linebreak between verb|XXX| and following stuff:}
emph{The code}
begingrouptopsep=0expartopsep=0ex
begin{verbatim*}
text XXX
1 A
end{verbatim*}%
endgroup
emph{yields:}\
text XXX
1 A
emph{The code}
begingrouptopsep=0expartopsep=0ex
begin{verbatim*}
text XXX
{1} A
end{verbatim*}
endgroup
emph{yields:}\
text XXX
{1} A
vfill
{bfseries Comment and linebreak between verb|XXX| and following stuff:}
emph{The code}
begingrouptopsep=0expartopsep=0ex
begin{verbatim*}
text XXX%
% Comment ^^M^^M Comment
% Comment
1 A
end{verbatim*}
endgroup
emph{yields:}\
text XXX%
% Comment ^^M^^M Comment
% Comment
1 A
vfill
{bfseries Comment and linebreak and linebreak between verb|XXX| and following stuff:}
emph{The code}
begingrouptopsep=0expartopsep=0ex
begin{verbatim*}
text XXX%
% Comment ^^M^^M Comment
% Comment
1 A
end{verbatim*}
endgroup
emph{yields:}\
begingroupparindent=0ex
text XXX%
% Comment ^^M^^M Comment
% Comment
1 A
endgroup
vfill
{bfseries Space between verb|XXX| and following stuff:}
verb*|text XXX 1 A|: text XXX 1 A
verb*|text XXX {1} A|: text XXX {1} A
vfill
{bfseries Horizontal tab between verb|XXX| and following stuff:}
verb*|text XXX 1 A|: text XXX 1 A
verb*|text XXX {1} A|: text XXX {1} A
vfill
{bfseries Nothing between verb|XXX| and following stuff:}
verb*|text XXX1 A|: text XXX1 A
verb*|text XXX{1} A|: text XXX{1} A
verb*|text XXX123 A|: text XXX123 A
verb*|text XXX{123} A|: text XXX{123} A
vfillvfillvfillvfillvfillvfill
end{document}
For some reason unknown to me horizontal-tabs in examples get transformed into four spaces when pasting code into the "Answer"-window of StackExchange.
Thus the horizontal-tabs I typed into the example before compiling it may during the copy-paste-process have been transformed to spaces which implies that the result of copy-pasting the example from StackExchange and compiling may have a look which is slightly different from the look of the image above.
I state clearly and explicitly that I do not recommend this approach. It has too many drawbacks and restrictions. The example is intended to show how confusing (La)TeX-programming can be. ;-)
One restriction is already mentioned: You can't use XXX
within macro-definitions or within macro-arguments. You'd better not have other macros deliver XXX
's argument or tokens that shall be behind XXX
but not be taken for XXX
's argument.
Some other restrictions and drawbacks are:
XXX
performs a lot of temporary assignments. ThusXXX
is not fully expandable and cannot be used safely in pure-expansion-contexts.
A thing like
XXX
could not be used safely within moving-arguments, i.e., within things that, e.g., get written to temporary files so that in future LaTeX-runs they can pop up in the table of contents or withinlabel
-ref
-cross-references also. One of the reasons for this is that LaTeX does always attach a space character when unexpanded-writing a control-word-token to an external text-file.
E.g.,
newwritemywrite
immediateopenoutmywrite experiment.tex %
immediatewritemywrite{noexpandXXX{Something}}%
immediatecloseoutmywrite
yields a file experiment.tex whose content is:
XXX␣{Something}
Note the space character between
XXX
and{Something}
.
Thanks. As I said in the update to my question: I was dissuaded from this approach. However, it is still interesting that this is possible, and see a way to do so.
– Boris Bukh
Feb 12 at 20:45
add a comment |
You can, e.g., create a command which has LaTeX temporarily change the catcode of space, tab, return and % (% is used for commenting) and via a variant of kernel@ifnextchar
(which itself is based on futurelet
) check the next token in the token-stream.
Some days ago, in my answer to the question Space after LaTeX commands, I tried to explain the drawbacks of the approach of having LaTeX "look ahead" at the next token:
The major drawback of this method is that it relies on the next token in the token-stream coming into being by reading and tokenizing tex-input from the tex-source-code while the temporary changes of these catcodes are effective.
But tokens can also get into the token-stream not by reading and tokenizing tex-input from the tex-source-code but as a result of expanding, e.g., a macro-token where both the replacement-text and the arguments got tokenized at points in time when these category-codes were not changed.
Commands that temporarily change the category-code-régime and rely on the changed category-code-régime being in effect when tokens which they shall process get tokenized cannot be used in circumstances where the things they shall process will already have been tokenized under the unchanged category-code-régime as would be the case, e.g., when they get their arguments passed by other macros as a result of expanding these other macros.
Therefore with the example below, XXX
is defined in terms of outer
for ensuring as good as possible that it will not be used within the definition-texts or the arguments of other macros.
You need another variant of kernel@ifnextchar
because you cannot safely use kernel@ifnextchar
as kernel@ifnextchar
is definitely not 100%ly reliable:
The commented sources of LaTeX 2e as a pdf-file whose name is source2e.pdf can be found at http://mirrors.ctan.org/macros/latex/base/source2e.pdf.
kernel@ifnextchar
in the LaTeX 2e sources is defined as follows—File d: ltdefns.dtx Date: 2018/09/26 Version v1.5e :
321 longdef@ifnextchar#1#2#3{%
322 letreserved@d=#1%
323 defreserved@a{#2}%
324 defreserved@b{#3}%
325 futurelet@let@token@ifnch}
326 letkernel@ifnextchar@ifnextchar
327 def@ifnch{%
328 ifx@let@token@sptoken
329 letreserved@c@xifnch
330 else
331 ifx@let@tokenreserved@d
332 letreserved@creserved@a
333 else
334 letreserved@creserved@b
335 fi
336 fi
337 reserved@c}
338 def:{let@sptoken= } : % this makes @sptoken a space token
339 def:{@xifnch} expandafterdef: {futurelet@let@token@ifnch}
E.g., you said you cannot test for a space. That's true. There was extra effort for implementing a loop that does remove spaces when implementing
kernel@ifnextchar
/@ifnextchar
.
This loop leads to error-messages with things like:
kernel@ifnextchar{⟨char⟩}{The next thing is ⟨char⟩}{The next thing is not ⟨char⟩}@sptoken
That has to do with the fact that knowing whether tokens have the same meaning does not imply knowing whether they are the same tokens.
When the token trailing the arguments of
kernel@ifnextchar
has the meaning of the tokenreserved@d
, i.e., when you have something like
kernel@ifnextchar{⟨char⟩}{The next thing is ⟨char⟩}{The next thing is not ⟨char⟩}reserved@d
, you get
kernel@ifnextchar
's second argument no matter what its first argument is.
(That's why things like
reserved@d
are reserved. ;-) )
See, e.g., what you get from
documentclass{article}
makeatletter
begin{document}
% This is nice:
kernel@ifnextchar{X}{The next thing is X}{The next thing is not X}reserved@d
kernel@ifnextchar{Y}{The next thing is Y}{The next thing is not Y}reserved@d
kernel@ifnextchar{Z}{The next thing is Z}{The next thing is not Z}reserved@d
kernel@ifnextchar{LaTeX}{The next thing is LaTeX}{The next thing is not LaTeX}reserved@d
% This raises nice errors.
% kernel@ifnextchar{X}{The next thing is X}{The next thing is not X}@sptoken X
end{document}
The reason for this is that
kernel@ifnextchar
does not distinguish different tokens from each other which have the same meaning. As a special case of this behavior,kernel@ifnextchar
does not distinguish implicit characters from explicit characters.
See, e.g., what you get from
documentclass{article}
makeatletter
letimplicitA=A
begin{document}
kernel@ifnextchar{A}{We have an }{We don't have an }implicitA
kernel@ifnextchar{implicitA}{We have an }{We don't have an }A
end{document}
Above it was said:
You can, e.g., create a command which has LaTeX temporarily change the catcode of space, tab, return and % (% is used for commenting) and via a variant of
kernel@ifnextchar
(which itself is based onfuturelet
) check the next token in the token-stream.
If the catcode of these characters is switched to 12(other) and checking only for these characters is of interest, you can have LaTeX peek at the meaning of the next token via futurelet
while leaving that token in place.
In case the meaning of the next token equals the meaning of one of these explicit catcode-12-character-tokens, you can safely have LaTeX grab that token as an undelimited macro argument. Thus in this special case of checking for explicit catcode-12-characters you can have LaTeX "grab" the token itself for defining a temporary macro that expands to that token. If there also is already defined another temporary argument that expands to your kernel@ifnextchar
-variant's first argument, LaTeX can do an ifx
-comparison with these temporary macros for making sure that the two tokens in question do not just have the same meanings but are really the same tokens. (When grabbing the next token as argument, LaTeX should probably put it back in the right moment...)
In the example below I tried to implement a routine UD@ifnextcharForOtherTokens
which causes LaTeX to do these things. At least I hope it does. ;-)
documentclass{article}
makeatletter
% Patch verbatim to also display horizontal tabs:
% The patch is needed for this example only.
usepackage{amssymb}
g@addto@macrodospecials{keystroketab}
newboxUD@tempbox
begingroup
catcode`^^I=13relax
@firstofone{%
endgroup
newcommandkeystroketab{%
setboxUD@tempboxhbox{verbatim@fontchar32}%
catcode`^^I=13relax
def^^I{mbox{hbox to 3wdUD@tempbox{nullhfill$leftrightarrows$hfillnull}}}%
}%
}%
%verbatim-patch done.
% UD@ifnextcharForOtherTokens peeks at the following token and
% compares it with its first argument w h i c h m u s t b e
% a s i n g l e t o k e n a n d w h i c h i n c a s e
% o f b e i n g a c h a r a c t er t o k e n --- b e
% i t e x p l i c i t o r i m p l i c i t --- m u s t
% b e a c h a r a c t e r t o k e n w h o s e e x p l i c i t
% v a r i a n t c a n b e s a f e l y g r a b b e d a s
% u n d e l i m i t e d a r g u m e n t!!! T h i s i s t h e
% c a s e, e. g., w i t h c h a r a c t er t o k e n s o f
% c a t e g o r y c o d e 12(other)!
% If both are the same it executes its second argument, otherwise
% its third.
newcommandUD@reserved@a{}%
newcommandUD@reserved@b{}%
newcommandUD@reserved@c{}%
newcommandUD@reserved@d{}%
newcommandUD@let@token{}%
newcommandUD@ifnextcharForOtherTokens[3]{%
begingroup
defUD@reserved@d{#1}%
defUD@reserved@a{#2}%
defUD@reserved@b{#3}%
futureletUD@let@tokenUD@ifnch
}%
newcommandUD@ifnch{%
expandafterifxexpandafterUD@let@tokenUD@reserved@d
expandafterUD@ifnchsnapnexttoken
else
expandafterexpandafterexpandafter
endgroupexpandafterUD@reserved@b
fi
}%
newcommandUD@ifnchsnapnexttoken[1]{%
defUD@reserved@c{#1}%
ifxUD@reserved@cUD@reserved@d
expandafterexpandafterexpandafter
endgroupexpandafterUD@reserved@a
else
expandafterexpandafterexpandafter
endgroupexpandafterUD@reserved@b
fi
#1%
}%
begingroup
% -------------------------------------------------
% Don't indent the following code!
% Each line of the following code must end with ^^A
% vwhich serves as comment-char!
catcode`^^A=14relax%
catcode` =12relax^^A
catcode`^^I=12relax^^A
catcode`^^M=12relax^^A
catcode`%=12relax^^A
@firstofone{^^A
endgroup^^A
newcommandUD@removeotherspace{}^^A
longdefUD@removeotherspace#1 {#1}^^A
newcommandUD@removeotherreturn{}^^A
longdefUD@removeotherreturn#1^^M{#1}^^A
newcommandUD@removeothertab{}^^A
longdefUD@removeothertab#1^^I{#1}^^A
newcommandUD@removecomment{}^^A
longdefUD@removecomment#1#2%#3^^M{endgroupUD@removecommentloop{#1}{#2}}^^A
begingroup^^A
newcommandUD@CheckWhetherSourceSpace[1]{^^A
endgroup^^A
newcommandUD@removecommentloop[2]{^^A
UD@ifnextcharForOtherTokens{ }{UD@removeotherspace{UD@removecommentloop{##1}{##2}}}{^^A
UD@ifnextcharForOtherTokens{^^I}{UD@removeothertab{UD@removecommentloop{##1}{##2}}}{^^A
UD@ifnextcharForOtherTokens{%}{begingroupcatcode`^=12#1UD@removecomment{##1}{##2}}{^^A
UD@ifnextcharForOtherTokens{^^M}{UD@removeotherreturn{endgroup##1{par}}}{endgroup##2}^^A
}}}}^^A
newcommandUD@CheckWhetherSourceSpace[2]{^^A
begingroup^^A
catcode` =12relax^^A
catcode`^^I=12relax^^A
catcode`^^M=12relax^^A
catcode`%=12relax^^A
UD@ifnextcharForOtherTokens{%}{begingroupcatcode`^=12#1UD@removecomment{##1}{##2}}{^^A
UD@ifnextcharForOtherTokens{ }{endgroupUD@removeotherspace{##1{#1ignorespaces}}}{^^A
UD@ifnextcharForOtherTokens{^^M}{endgroupUD@removeotherreturn{##1{#1ignorespaces}}}{^^A
UD@ifnextcharForOtherTokens{^^I}{endgroupUD@removeothertab{##1{#1ignorespaces}}}{endgroup##2}^^A
}}}}}^^A
}%<- closing brace of @firstofone's argument
UD@CheckWhetherSourceSpace{ }%
%
% Now we are back to normal circumstances. ;-)
% -------------------------------------------------
%
% !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
% Commands that call UD@CheckWhetherSourceSpace must be defined in
% terms of outer.
% They may be used only in circumstances where their arguments get
% read and tokenized _while_ they are carried out.
% They must not be used in circumstances where the tokens probably
% forming their arguments already got tokenized and then were passed
% on to them via "spitting out" some macro-definition or
% macro-argument or the like.
% Be aware that defining in terms of outer does not prevent
% erroneous usage to 100%,
% !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
%
% The first argument of UD@CheckWhetherSourceSpace is a macro that
% performs the action in case no argument is present. It seems
% confusing but it must nonetheless process one argument.
% That argument is deliveredby UD@CheckWhetherSourceSpace as a means
% of providing info about whether a space token or a par-token
% needs to be appended after the action.
% The second argument of UD@CheckWhetherSourceSpace is a macro that
% performs the action in case an argument is present.
newcommandXXX{}
outerdefXXX{%
UD@CheckWhetherSourceSpace{XXX@Space}{XXX@Arg}%
}%
newcommandXXX@Space[1]{fbox{Bye}#1}%
newcommandXXX@Arg[1]{fbox{Hello #1}}
makeatother
parindent=-.66cm
begin{document}
{bfseries Linebreak between verb|XXX| and following stuff:}
emph{The code}
begingrouptopsep=0expartopsep=0ex
begin{verbatim*}
text XXX
1 A
end{verbatim*}%
endgroup
emph{yields:}\
text XXX
1 A
emph{The code}
begingrouptopsep=0expartopsep=0ex
begin{verbatim*}
text XXX
{1} A
end{verbatim*}
endgroup
emph{yields:}\
text XXX
{1} A
vfill
{bfseries Comment and linebreak between verb|XXX| and following stuff:}
emph{The code}
begingrouptopsep=0expartopsep=0ex
begin{verbatim*}
text XXX%
% Comment ^^M^^M Comment
% Comment
1 A
end{verbatim*}
endgroup
emph{yields:}\
text XXX%
% Comment ^^M^^M Comment
% Comment
1 A
vfill
{bfseries Comment and linebreak and linebreak between verb|XXX| and following stuff:}
emph{The code}
begingrouptopsep=0expartopsep=0ex
begin{verbatim*}
text XXX%
% Comment ^^M^^M Comment
% Comment
1 A
end{verbatim*}
endgroup
emph{yields:}\
begingroupparindent=0ex
text XXX%
% Comment ^^M^^M Comment
% Comment
1 A
endgroup
vfill
{bfseries Space between verb|XXX| and following stuff:}
verb*|text XXX 1 A|: text XXX 1 A
verb*|text XXX {1} A|: text XXX {1} A
vfill
{bfseries Horizontal tab between verb|XXX| and following stuff:}
verb*|text XXX 1 A|: text XXX 1 A
verb*|text XXX {1} A|: text XXX {1} A
vfill
{bfseries Nothing between verb|XXX| and following stuff:}
verb*|text XXX1 A|: text XXX1 A
verb*|text XXX{1} A|: text XXX{1} A
verb*|text XXX123 A|: text XXX123 A
verb*|text XXX{123} A|: text XXX{123} A
vfillvfillvfillvfillvfillvfill
end{document}
For some reason unknown to me horizontal-tabs in examples get transformed into four spaces when pasting code into the "Answer"-window of StackExchange.
Thus the horizontal-tabs I typed into the example before compiling it may during the copy-paste-process have been transformed to spaces which implies that the result of copy-pasting the example from StackExchange and compiling may have a look which is slightly different from the look of the image above.
I state clearly and explicitly that I do not recommend this approach. It has too many drawbacks and restrictions. The example is intended to show how confusing (La)TeX-programming can be. ;-)
One restriction is already mentioned: You can't use XXX
within macro-definitions or within macro-arguments. You'd better not have other macros deliver XXX
's argument or tokens that shall be behind XXX
but not be taken for XXX
's argument.
Some other restrictions and drawbacks are:
XXX
performs a lot of temporary assignments. ThusXXX
is not fully expandable and cannot be used safely in pure-expansion-contexts.
A thing like
XXX
could not be used safely within moving-arguments, i.e., within things that, e.g., get written to temporary files so that in future LaTeX-runs they can pop up in the table of contents or withinlabel
-ref
-cross-references also. One of the reasons for this is that LaTeX does always attach a space character when unexpanded-writing a control-word-token to an external text-file.
E.g.,
newwritemywrite
immediateopenoutmywrite experiment.tex %
immediatewritemywrite{noexpandXXX{Something}}%
immediatecloseoutmywrite
yields a file experiment.tex whose content is:
XXX␣{Something}
Note the space character between
XXX
and{Something}
.
Thanks. As I said in the update to my question: I was dissuaded from this approach. However, it is still interesting that this is possible, and see a way to do so.
– Boris Bukh
Feb 12 at 20:45
add a comment |
You can, e.g., create a command which has LaTeX temporarily change the catcode of space, tab, return and % (% is used for commenting) and via a variant of kernel@ifnextchar
(which itself is based on futurelet
) check the next token in the token-stream.
Some days ago, in my answer to the question Space after LaTeX commands, I tried to explain the drawbacks of the approach of having LaTeX "look ahead" at the next token:
The major drawback of this method is that it relies on the next token in the token-stream coming into being by reading and tokenizing tex-input from the tex-source-code while the temporary changes of these catcodes are effective.
But tokens can also get into the token-stream not by reading and tokenizing tex-input from the tex-source-code but as a result of expanding, e.g., a macro-token where both the replacement-text and the arguments got tokenized at points in time when these category-codes were not changed.
Commands that temporarily change the category-code-régime and rely on the changed category-code-régime being in effect when tokens which they shall process get tokenized cannot be used in circumstances where the things they shall process will already have been tokenized under the unchanged category-code-régime as would be the case, e.g., when they get their arguments passed by other macros as a result of expanding these other macros.
Therefore with the example below, XXX
is defined in terms of outer
for ensuring as good as possible that it will not be used within the definition-texts or the arguments of other macros.
You need another variant of kernel@ifnextchar
because you cannot safely use kernel@ifnextchar
as kernel@ifnextchar
is definitely not 100%ly reliable:
The commented sources of LaTeX 2e as a pdf-file whose name is source2e.pdf can be found at http://mirrors.ctan.org/macros/latex/base/source2e.pdf.
kernel@ifnextchar
in the LaTeX 2e sources is defined as follows—File d: ltdefns.dtx Date: 2018/09/26 Version v1.5e :
321 longdef@ifnextchar#1#2#3{%
322 letreserved@d=#1%
323 defreserved@a{#2}%
324 defreserved@b{#3}%
325 futurelet@let@token@ifnch}
326 letkernel@ifnextchar@ifnextchar
327 def@ifnch{%
328 ifx@let@token@sptoken
329 letreserved@c@xifnch
330 else
331 ifx@let@tokenreserved@d
332 letreserved@creserved@a
333 else
334 letreserved@creserved@b
335 fi
336 fi
337 reserved@c}
338 def:{let@sptoken= } : % this makes @sptoken a space token
339 def:{@xifnch} expandafterdef: {futurelet@let@token@ifnch}
E.g., you said you cannot test for a space. That's true. There was extra effort for implementing a loop that does remove spaces when implementing
kernel@ifnextchar
/@ifnextchar
.
This loop leads to error-messages with things like:
kernel@ifnextchar{⟨char⟩}{The next thing is ⟨char⟩}{The next thing is not ⟨char⟩}@sptoken
That has to do with the fact that knowing whether tokens have the same meaning does not imply knowing whether they are the same tokens.
When the token trailing the arguments of
kernel@ifnextchar
has the meaning of the tokenreserved@d
, i.e., when you have something like
kernel@ifnextchar{⟨char⟩}{The next thing is ⟨char⟩}{The next thing is not ⟨char⟩}reserved@d
, you get
kernel@ifnextchar
's second argument no matter what its first argument is.
(That's why things like
reserved@d
are reserved. ;-) )
See, e.g., what you get from
documentclass{article}
makeatletter
begin{document}
% This is nice:
kernel@ifnextchar{X}{The next thing is X}{The next thing is not X}reserved@d
kernel@ifnextchar{Y}{The next thing is Y}{The next thing is not Y}reserved@d
kernel@ifnextchar{Z}{The next thing is Z}{The next thing is not Z}reserved@d
kernel@ifnextchar{LaTeX}{The next thing is LaTeX}{The next thing is not LaTeX}reserved@d
% This raises nice errors.
% kernel@ifnextchar{X}{The next thing is X}{The next thing is not X}@sptoken X
end{document}
The reason for this is that
kernel@ifnextchar
does not distinguish different tokens from each other which have the same meaning. As a special case of this behavior,kernel@ifnextchar
does not distinguish implicit characters from explicit characters.
See, e.g., what you get from
documentclass{article}
makeatletter
letimplicitA=A
begin{document}
kernel@ifnextchar{A}{We have an }{We don't have an }implicitA
kernel@ifnextchar{implicitA}{We have an }{We don't have an }A
end{document}
Above it was said:
You can, e.g., create a command which has LaTeX temporarily change the catcode of space, tab, return and % (% is used for commenting) and via a variant of
kernel@ifnextchar
(which itself is based onfuturelet
) check the next token in the token-stream.
If the catcode of these characters is switched to 12(other) and checking only for these characters is of interest, you can have LaTeX peek at the meaning of the next token via futurelet
while leaving that token in place.
In case the meaning of the next token equals the meaning of one of these explicit catcode-12-character-tokens, you can safely have LaTeX grab that token as an undelimited macro argument. Thus in this special case of checking for explicit catcode-12-characters you can have LaTeX "grab" the token itself for defining a temporary macro that expands to that token. If there also is already defined another temporary argument that expands to your kernel@ifnextchar
-variant's first argument, LaTeX can do an ifx
-comparison with these temporary macros for making sure that the two tokens in question do not just have the same meanings but are really the same tokens. (When grabbing the next token as argument, LaTeX should probably put it back in the right moment...)
In the example below I tried to implement a routine UD@ifnextcharForOtherTokens
which causes LaTeX to do these things. At least I hope it does. ;-)
documentclass{article}
makeatletter
% Patch verbatim to also display horizontal tabs:
% The patch is needed for this example only.
usepackage{amssymb}
g@addto@macrodospecials{keystroketab}
newboxUD@tempbox
begingroup
catcode`^^I=13relax
@firstofone{%
endgroup
newcommandkeystroketab{%
setboxUD@tempboxhbox{verbatim@fontchar32}%
catcode`^^I=13relax
def^^I{mbox{hbox to 3wdUD@tempbox{nullhfill$leftrightarrows$hfillnull}}}%
}%
}%
%verbatim-patch done.
% UD@ifnextcharForOtherTokens peeks at the following token and
% compares it with its first argument w h i c h m u s t b e
% a s i n g l e t o k e n a n d w h i c h i n c a s e
% o f b e i n g a c h a r a c t er t o k e n --- b e
% i t e x p l i c i t o r i m p l i c i t --- m u s t
% b e a c h a r a c t e r t o k e n w h o s e e x p l i c i t
% v a r i a n t c a n b e s a f e l y g r a b b e d a s
% u n d e l i m i t e d a r g u m e n t!!! T h i s i s t h e
% c a s e, e. g., w i t h c h a r a c t er t o k e n s o f
% c a t e g o r y c o d e 12(other)!
% If both are the same it executes its second argument, otherwise
% its third.
newcommandUD@reserved@a{}%
newcommandUD@reserved@b{}%
newcommandUD@reserved@c{}%
newcommandUD@reserved@d{}%
newcommandUD@let@token{}%
newcommandUD@ifnextcharForOtherTokens[3]{%
begingroup
defUD@reserved@d{#1}%
defUD@reserved@a{#2}%
defUD@reserved@b{#3}%
futureletUD@let@tokenUD@ifnch
}%
newcommandUD@ifnch{%
expandafterifxexpandafterUD@let@tokenUD@reserved@d
expandafterUD@ifnchsnapnexttoken
else
expandafterexpandafterexpandafter
endgroupexpandafterUD@reserved@b
fi
}%
newcommandUD@ifnchsnapnexttoken[1]{%
defUD@reserved@c{#1}%
ifxUD@reserved@cUD@reserved@d
expandafterexpandafterexpandafter
endgroupexpandafterUD@reserved@a
else
expandafterexpandafterexpandafter
endgroupexpandafterUD@reserved@b
fi
#1%
}%
begingroup
% -------------------------------------------------
% Don't indent the following code!
% Each line of the following code must end with ^^A
% vwhich serves as comment-char!
catcode`^^A=14relax%
catcode` =12relax^^A
catcode`^^I=12relax^^A
catcode`^^M=12relax^^A
catcode`%=12relax^^A
@firstofone{^^A
endgroup^^A
newcommandUD@removeotherspace{}^^A
longdefUD@removeotherspace#1 {#1}^^A
newcommandUD@removeotherreturn{}^^A
longdefUD@removeotherreturn#1^^M{#1}^^A
newcommandUD@removeothertab{}^^A
longdefUD@removeothertab#1^^I{#1}^^A
newcommandUD@removecomment{}^^A
longdefUD@removecomment#1#2%#3^^M{endgroupUD@removecommentloop{#1}{#2}}^^A
begingroup^^A
newcommandUD@CheckWhetherSourceSpace[1]{^^A
endgroup^^A
newcommandUD@removecommentloop[2]{^^A
UD@ifnextcharForOtherTokens{ }{UD@removeotherspace{UD@removecommentloop{##1}{##2}}}{^^A
UD@ifnextcharForOtherTokens{^^I}{UD@removeothertab{UD@removecommentloop{##1}{##2}}}{^^A
UD@ifnextcharForOtherTokens{%}{begingroupcatcode`^=12#1UD@removecomment{##1}{##2}}{^^A
UD@ifnextcharForOtherTokens{^^M}{UD@removeotherreturn{endgroup##1{par}}}{endgroup##2}^^A
}}}}^^A
newcommandUD@CheckWhetherSourceSpace[2]{^^A
begingroup^^A
catcode` =12relax^^A
catcode`^^I=12relax^^A
catcode`^^M=12relax^^A
catcode`%=12relax^^A
UD@ifnextcharForOtherTokens{%}{begingroupcatcode`^=12#1UD@removecomment{##1}{##2}}{^^A
UD@ifnextcharForOtherTokens{ }{endgroupUD@removeotherspace{##1{#1ignorespaces}}}{^^A
UD@ifnextcharForOtherTokens{^^M}{endgroupUD@removeotherreturn{##1{#1ignorespaces}}}{^^A
UD@ifnextcharForOtherTokens{^^I}{endgroupUD@removeothertab{##1{#1ignorespaces}}}{endgroup##2}^^A
}}}}}^^A
}%<- closing brace of @firstofone's argument
UD@CheckWhetherSourceSpace{ }%
%
% Now we are back to normal circumstances. ;-)
% -------------------------------------------------
%
% !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
% Commands that call UD@CheckWhetherSourceSpace must be defined in
% terms of outer.
% They may be used only in circumstances where their arguments get
% read and tokenized _while_ they are carried out.
% They must not be used in circumstances where the tokens probably
% forming their arguments already got tokenized and then were passed
% on to them via "spitting out" some macro-definition or
% macro-argument or the like.
% Be aware that defining in terms of outer does not prevent
% erroneous usage to 100%,
% !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
%
% The first argument of UD@CheckWhetherSourceSpace is a macro that
% performs the action in case no argument is present. It seems
% confusing but it must nonetheless process one argument.
% That argument is deliveredby UD@CheckWhetherSourceSpace as a means
% of providing info about whether a space token or a par-token
% needs to be appended after the action.
% The second argument of UD@CheckWhetherSourceSpace is a macro that
% performs the action in case an argument is present.
newcommandXXX{}
outerdefXXX{%
UD@CheckWhetherSourceSpace{XXX@Space}{XXX@Arg}%
}%
newcommandXXX@Space[1]{fbox{Bye}#1}%
newcommandXXX@Arg[1]{fbox{Hello #1}}
makeatother
parindent=-.66cm
begin{document}
{bfseries Linebreak between verb|XXX| and following stuff:}
emph{The code}
begingrouptopsep=0expartopsep=0ex
begin{verbatim*}
text XXX
1 A
end{verbatim*}%
endgroup
emph{yields:}\
text XXX
1 A
emph{The code}
begingrouptopsep=0expartopsep=0ex
begin{verbatim*}
text XXX
{1} A
end{verbatim*}
endgroup
emph{yields:}\
text XXX
{1} A
vfill
{bfseries Comment and linebreak between verb|XXX| and following stuff:}
emph{The code}
begingrouptopsep=0expartopsep=0ex
begin{verbatim*}
text XXX%
% Comment ^^M^^M Comment
% Comment
1 A
end{verbatim*}
endgroup
emph{yields:}\
text XXX%
% Comment ^^M^^M Comment
% Comment
1 A
vfill
{bfseries Comment and linebreak and linebreak between verb|XXX| and following stuff:}
emph{The code}
begingrouptopsep=0expartopsep=0ex
begin{verbatim*}
text XXX%
% Comment ^^M^^M Comment
% Comment
1 A
end{verbatim*}
endgroup
emph{yields:}\
begingroupparindent=0ex
text XXX%
% Comment ^^M^^M Comment
% Comment
1 A
endgroup
vfill
{bfseries Space between verb|XXX| and following stuff:}
verb*|text XXX 1 A|: text XXX 1 A
verb*|text XXX {1} A|: text XXX {1} A
vfill
{bfseries Horizontal tab between verb|XXX| and following stuff:}
verb*|text XXX 1 A|: text XXX 1 A
verb*|text XXX {1} A|: text XXX {1} A
vfill
{bfseries Nothing between verb|XXX| and following stuff:}
verb*|text XXX1 A|: text XXX1 A
verb*|text XXX{1} A|: text XXX{1} A
verb*|text XXX123 A|: text XXX123 A
verb*|text XXX{123} A|: text XXX{123} A
vfillvfillvfillvfillvfillvfill
end{document}
For some reason unknown to me horizontal-tabs in examples get transformed into four spaces when pasting code into the "Answer"-window of StackExchange.
Thus the horizontal-tabs I typed into the example before compiling it may during the copy-paste-process have been transformed to spaces which implies that the result of copy-pasting the example from StackExchange and compiling may have a look which is slightly different from the look of the image above.
I state clearly and explicitly that I do not recommend this approach. It has too many drawbacks and restrictions. The example is intended to show how confusing (La)TeX-programming can be. ;-)
One restriction is already mentioned: You can't use XXX
within macro-definitions or within macro-arguments. You'd better not have other macros deliver XXX
's argument or tokens that shall be behind XXX
but not be taken for XXX
's argument.
Some other restrictions and drawbacks are:
XXX
performs a lot of temporary assignments. ThusXXX
is not fully expandable and cannot be used safely in pure-expansion-contexts.
A thing like
XXX
could not be used safely within moving-arguments, i.e., within things that, e.g., get written to temporary files so that in future LaTeX-runs they can pop up in the table of contents or withinlabel
-ref
-cross-references also. One of the reasons for this is that LaTeX does always attach a space character when unexpanded-writing a control-word-token to an external text-file.
E.g.,
newwritemywrite
immediateopenoutmywrite experiment.tex %
immediatewritemywrite{noexpandXXX{Something}}%
immediatecloseoutmywrite
yields a file experiment.tex whose content is:
XXX␣{Something}
Note the space character between
XXX
and{Something}
.
You can, e.g., create a command which has LaTeX temporarily change the catcode of space, tab, return and % (% is used for commenting) and via a variant of kernel@ifnextchar
(which itself is based on futurelet
) check the next token in the token-stream.
Some days ago, in my answer to the question Space after LaTeX commands, I tried to explain the drawbacks of the approach of having LaTeX "look ahead" at the next token:
The major drawback of this method is that it relies on the next token in the token-stream coming into being by reading and tokenizing tex-input from the tex-source-code while the temporary changes of these catcodes are effective.
But tokens can also get into the token-stream not by reading and tokenizing tex-input from the tex-source-code but as a result of expanding, e.g., a macro-token where both the replacement-text and the arguments got tokenized at points in time when these category-codes were not changed.
Commands that temporarily change the category-code-régime and rely on the changed category-code-régime being in effect when tokens which they shall process get tokenized cannot be used in circumstances where the things they shall process will already have been tokenized under the unchanged category-code-régime as would be the case, e.g., when they get their arguments passed by other macros as a result of expanding these other macros.
Therefore with the example below, XXX
is defined in terms of outer
for ensuring as good as possible that it will not be used within the definition-texts or the arguments of other macros.
You need another variant of kernel@ifnextchar
because you cannot safely use kernel@ifnextchar
as kernel@ifnextchar
is definitely not 100%ly reliable:
The commented sources of LaTeX 2e as a pdf-file whose name is source2e.pdf can be found at http://mirrors.ctan.org/macros/latex/base/source2e.pdf.
kernel@ifnextchar
in the LaTeX 2e sources is defined as follows—File d: ltdefns.dtx Date: 2018/09/26 Version v1.5e :
321 longdef@ifnextchar#1#2#3{%
322 letreserved@d=#1%
323 defreserved@a{#2}%
324 defreserved@b{#3}%
325 futurelet@let@token@ifnch}
326 letkernel@ifnextchar@ifnextchar
327 def@ifnch{%
328 ifx@let@token@sptoken
329 letreserved@c@xifnch
330 else
331 ifx@let@tokenreserved@d
332 letreserved@creserved@a
333 else
334 letreserved@creserved@b
335 fi
336 fi
337 reserved@c}
338 def:{let@sptoken= } : % this makes @sptoken a space token
339 def:{@xifnch} expandafterdef: {futurelet@let@token@ifnch}
E.g., you said you cannot test for a space. That's true. There was extra effort for implementing a loop that does remove spaces when implementing
kernel@ifnextchar
/@ifnextchar
.
This loop leads to error-messages with things like:
kernel@ifnextchar{⟨char⟩}{The next thing is ⟨char⟩}{The next thing is not ⟨char⟩}@sptoken
That has to do with the fact that knowing whether tokens have the same meaning does not imply knowing whether they are the same tokens.
When the token trailing the arguments of
kernel@ifnextchar
has the meaning of the tokenreserved@d
, i.e., when you have something like
kernel@ifnextchar{⟨char⟩}{The next thing is ⟨char⟩}{The next thing is not ⟨char⟩}reserved@d
, you get
kernel@ifnextchar
's second argument no matter what its first argument is.
(That's why things like
reserved@d
are reserved. ;-) )
See, e.g., what you get from
documentclass{article}
makeatletter
begin{document}
% This is nice:
kernel@ifnextchar{X}{The next thing is X}{The next thing is not X}reserved@d
kernel@ifnextchar{Y}{The next thing is Y}{The next thing is not Y}reserved@d
kernel@ifnextchar{Z}{The next thing is Z}{The next thing is not Z}reserved@d
kernel@ifnextchar{LaTeX}{The next thing is LaTeX}{The next thing is not LaTeX}reserved@d
% This raises nice errors.
% kernel@ifnextchar{X}{The next thing is X}{The next thing is not X}@sptoken X
end{document}
The reason for this is that
kernel@ifnextchar
does not distinguish different tokens from each other which have the same meaning. As a special case of this behavior,kernel@ifnextchar
does not distinguish implicit characters from explicit characters.
See, e.g., what you get from
documentclass{article}
makeatletter
letimplicitA=A
begin{document}
kernel@ifnextchar{A}{We have an }{We don't have an }implicitA
kernel@ifnextchar{implicitA}{We have an }{We don't have an }A
end{document}
Above it was said:
You can, e.g., create a command which has LaTeX temporarily change the catcode of space, tab, return and % (% is used for commenting) and via a variant of
kernel@ifnextchar
(which itself is based onfuturelet
) check the next token in the token-stream.
If the catcode of these characters is switched to 12(other) and checking only for these characters is of interest, you can have LaTeX peek at the meaning of the next token via futurelet
while leaving that token in place.
In case the meaning of the next token equals the meaning of one of these explicit catcode-12-character-tokens, you can safely have LaTeX grab that token as an undelimited macro argument. Thus in this special case of checking for explicit catcode-12-characters you can have LaTeX "grab" the token itself for defining a temporary macro that expands to that token. If there also is already defined another temporary argument that expands to your kernel@ifnextchar
-variant's first argument, LaTeX can do an ifx
-comparison with these temporary macros for making sure that the two tokens in question do not just have the same meanings but are really the same tokens. (When grabbing the next token as argument, LaTeX should probably put it back in the right moment...)
In the example below I tried to implement a routine UD@ifnextcharForOtherTokens
which causes LaTeX to do these things. At least I hope it does. ;-)
documentclass{article}
makeatletter
% Patch verbatim to also display horizontal tabs:
% The patch is needed for this example only.
usepackage{amssymb}
g@addto@macrodospecials{keystroketab}
newboxUD@tempbox
begingroup
catcode`^^I=13relax
@firstofone{%
endgroup
newcommandkeystroketab{%
setboxUD@tempboxhbox{verbatim@fontchar32}%
catcode`^^I=13relax
def^^I{mbox{hbox to 3wdUD@tempbox{nullhfill$leftrightarrows$hfillnull}}}%
}%
}%
%verbatim-patch done.
% UD@ifnextcharForOtherTokens peeks at the following token and
% compares it with its first argument w h i c h m u s t b e
% a s i n g l e t o k e n a n d w h i c h i n c a s e
% o f b e i n g a c h a r a c t er t o k e n --- b e
% i t e x p l i c i t o r i m p l i c i t --- m u s t
% b e a c h a r a c t e r t o k e n w h o s e e x p l i c i t
% v a r i a n t c a n b e s a f e l y g r a b b e d a s
% u n d e l i m i t e d a r g u m e n t!!! T h i s i s t h e
% c a s e, e. g., w i t h c h a r a c t er t o k e n s o f
% c a t e g o r y c o d e 12(other)!
% If both are the same it executes its second argument, otherwise
% its third.
newcommandUD@reserved@a{}%
newcommandUD@reserved@b{}%
newcommandUD@reserved@c{}%
newcommandUD@reserved@d{}%
newcommandUD@let@token{}%
newcommandUD@ifnextcharForOtherTokens[3]{%
begingroup
defUD@reserved@d{#1}%
defUD@reserved@a{#2}%
defUD@reserved@b{#3}%
futureletUD@let@tokenUD@ifnch
}%
newcommandUD@ifnch{%
expandafterifxexpandafterUD@let@tokenUD@reserved@d
expandafterUD@ifnchsnapnexttoken
else
expandafterexpandafterexpandafter
endgroupexpandafterUD@reserved@b
fi
}%
newcommandUD@ifnchsnapnexttoken[1]{%
defUD@reserved@c{#1}%
ifxUD@reserved@cUD@reserved@d
expandafterexpandafterexpandafter
endgroupexpandafterUD@reserved@a
else
expandafterexpandafterexpandafter
endgroupexpandafterUD@reserved@b
fi
#1%
}%
begingroup
% -------------------------------------------------
% Don't indent the following code!
% Each line of the following code must end with ^^A
% vwhich serves as comment-char!
catcode`^^A=14relax%
catcode` =12relax^^A
catcode`^^I=12relax^^A
catcode`^^M=12relax^^A
catcode`%=12relax^^A
@firstofone{^^A
endgroup^^A
newcommandUD@removeotherspace{}^^A
longdefUD@removeotherspace#1 {#1}^^A
newcommandUD@removeotherreturn{}^^A
longdefUD@removeotherreturn#1^^M{#1}^^A
newcommandUD@removeothertab{}^^A
longdefUD@removeothertab#1^^I{#1}^^A
newcommandUD@removecomment{}^^A
longdefUD@removecomment#1#2%#3^^M{endgroupUD@removecommentloop{#1}{#2}}^^A
begingroup^^A
newcommandUD@CheckWhetherSourceSpace[1]{^^A
endgroup^^A
newcommandUD@removecommentloop[2]{^^A
UD@ifnextcharForOtherTokens{ }{UD@removeotherspace{UD@removecommentloop{##1}{##2}}}{^^A
UD@ifnextcharForOtherTokens{^^I}{UD@removeothertab{UD@removecommentloop{##1}{##2}}}{^^A
UD@ifnextcharForOtherTokens{%}{begingroupcatcode`^=12#1UD@removecomment{##1}{##2}}{^^A
UD@ifnextcharForOtherTokens{^^M}{UD@removeotherreturn{endgroup##1{par}}}{endgroup##2}^^A
}}}}^^A
newcommandUD@CheckWhetherSourceSpace[2]{^^A
begingroup^^A
catcode` =12relax^^A
catcode`^^I=12relax^^A
catcode`^^M=12relax^^A
catcode`%=12relax^^A
UD@ifnextcharForOtherTokens{%}{begingroupcatcode`^=12#1UD@removecomment{##1}{##2}}{^^A
UD@ifnextcharForOtherTokens{ }{endgroupUD@removeotherspace{##1{#1ignorespaces}}}{^^A
UD@ifnextcharForOtherTokens{^^M}{endgroupUD@removeotherreturn{##1{#1ignorespaces}}}{^^A
UD@ifnextcharForOtherTokens{^^I}{endgroupUD@removeothertab{##1{#1ignorespaces}}}{endgroup##2}^^A
}}}}}^^A
}%<- closing brace of @firstofone's argument
UD@CheckWhetherSourceSpace{ }%
%
% Now we are back to normal circumstances. ;-)
% -------------------------------------------------
%
% !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
% Commands that call UD@CheckWhetherSourceSpace must be defined in
% terms of outer.
% They may be used only in circumstances where their arguments get
% read and tokenized _while_ they are carried out.
% They must not be used in circumstances where the tokens probably
% forming their arguments already got tokenized and then were passed
% on to them via "spitting out" some macro-definition or
% macro-argument or the like.
% Be aware that defining in terms of outer does not prevent
% erroneous usage to 100%,
% !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
%
% The first argument of UD@CheckWhetherSourceSpace is a macro that
% performs the action in case no argument is present. It seems
% confusing but it must nonetheless process one argument.
% That argument is deliveredby UD@CheckWhetherSourceSpace as a means
% of providing info about whether a space token or a par-token
% needs to be appended after the action.
% The second argument of UD@CheckWhetherSourceSpace is a macro that
% performs the action in case an argument is present.
newcommandXXX{}
outerdefXXX{%
UD@CheckWhetherSourceSpace{XXX@Space}{XXX@Arg}%
}%
newcommandXXX@Space[1]{fbox{Bye}#1}%
newcommandXXX@Arg[1]{fbox{Hello #1}}
makeatother
parindent=-.66cm
begin{document}
{bfseries Linebreak between verb|XXX| and following stuff:}
emph{The code}
begingrouptopsep=0expartopsep=0ex
begin{verbatim*}
text XXX
1 A
end{verbatim*}%
endgroup
emph{yields:}\
text XXX
1 A
emph{The code}
begingrouptopsep=0expartopsep=0ex
begin{verbatim*}
text XXX
{1} A
end{verbatim*}
endgroup
emph{yields:}\
text XXX
{1} A
vfill
{bfseries Comment and linebreak between verb|XXX| and following stuff:}
emph{The code}
begingrouptopsep=0expartopsep=0ex
begin{verbatim*}
text XXX%
% Comment ^^M^^M Comment
% Comment
1 A
end{verbatim*}
endgroup
emph{yields:}\
text XXX%
% Comment ^^M^^M Comment
% Comment
1 A
vfill
{bfseries Comment and linebreak and linebreak between verb|XXX| and following stuff:}
emph{The code}
begingrouptopsep=0expartopsep=0ex
begin{verbatim*}
text XXX%
% Comment ^^M^^M Comment
% Comment
1 A
end{verbatim*}
endgroup
emph{yields:}\
begingroupparindent=0ex
text XXX%
% Comment ^^M^^M Comment
% Comment
1 A
endgroup
vfill
{bfseries Space between verb|XXX| and following stuff:}
verb*|text XXX 1 A|: text XXX 1 A
verb*|text XXX {1} A|: text XXX {1} A
vfill
{bfseries Horizontal tab between verb|XXX| and following stuff:}
verb*|text XXX 1 A|: text XXX 1 A
verb*|text XXX {1} A|: text XXX {1} A
vfill
{bfseries Nothing between verb|XXX| and following stuff:}
verb*|text XXX1 A|: text XXX1 A
verb*|text XXX{1} A|: text XXX{1} A
verb*|text XXX123 A|: text XXX123 A
verb*|text XXX{123} A|: text XXX{123} A
vfillvfillvfillvfillvfillvfill
end{document}
For some reason unknown to me horizontal-tabs in examples get transformed into four spaces when pasting code into the "Answer"-window of StackExchange.
Thus the horizontal-tabs I typed into the example before compiling it may during the copy-paste-process have been transformed to spaces which implies that the result of copy-pasting the example from StackExchange and compiling may have a look which is slightly different from the look of the image above.
I state clearly and explicitly that I do not recommend this approach. It has too many drawbacks and restrictions. The example is intended to show how confusing (La)TeX-programming can be. ;-)
One restriction is already mentioned: You can't use XXX
within macro-definitions or within macro-arguments. You'd better not have other macros deliver XXX
's argument or tokens that shall be behind XXX
but not be taken for XXX
's argument.
Some other restrictions and drawbacks are:
XXX
performs a lot of temporary assignments. ThusXXX
is not fully expandable and cannot be used safely in pure-expansion-contexts.
A thing like
XXX
could not be used safely within moving-arguments, i.e., within things that, e.g., get written to temporary files so that in future LaTeX-runs they can pop up in the table of contents or withinlabel
-ref
-cross-references also. One of the reasons for this is that LaTeX does always attach a space character when unexpanded-writing a control-word-token to an external text-file.
E.g.,
newwritemywrite
immediateopenoutmywrite experiment.tex %
immediatewritemywrite{noexpandXXX{Something}}%
immediatecloseoutmywrite
yields a file experiment.tex whose content is:
XXX␣{Something}
Note the space character between
XXX
and{Something}
.
edited 22 hours ago
answered Feb 12 at 18:42
Ulrich DiezUlrich Diez
4,865618
4,865618
Thanks. As I said in the update to my question: I was dissuaded from this approach. However, it is still interesting that this is possible, and see a way to do so.
– Boris Bukh
Feb 12 at 20:45
add a comment |
Thanks. As I said in the update to my question: I was dissuaded from this approach. However, it is still interesting that this is possible, and see a way to do so.
– Boris Bukh
Feb 12 at 20:45
Thanks. As I said in the update to my question: I was dissuaded from this approach. However, it is still interesting that this is possible, and see a way to do so.
– Boris Bukh
Feb 12 at 20:45
Thanks. As I said in the update to my question: I was dissuaded from this approach. However, it is still interesting that this is possible, and see a way to do so.
– Boris Bukh
Feb 12 at 20:45
add a comment |
Thanks for contributing an answer to TeX - LaTeX Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2ftex.stackexchange.com%2fquestions%2f474424%2fdifferent-behavior-if-no-arguments%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
If you could agree on using
[1]
and[2]
instead of1
and2
, you could dodocumentclass{article} begin{document} newcommand{XXX}[1][empty]{ifx#1empty Bye else Hello #1 fi} XXX[1] XXX end{document}
.– marmot
Feb 11 at 22:29
Can you show how you would use
XXX
in general? If you want the "mandatory" argument to be conditional, then there might be instances where you're forced to introduce some weird syntax so as to not grab content. For example, willXXX
only accept numbers, or characters as well? If numbers, single or double digits or higher order digits? Will you use input likeXXX1
,XXX{1}
andXXX 1
interchangeably?– Werner
Feb 11 at 22:39
1
@marmot oh sorry I just noticed your comment, which has some similarities to my answer:-)
– David Carlisle
Feb 11 at 22:57
1
what do you mean by
XXX
by itself?XXX1
is the same asXXX 1
so what input followingXXX
would you consider as terminating the construct with no argument?– David Carlisle
Feb 11 at 23:01
@marmot I can't guess what you mean about
else
, but tryXXX[11]
...– David Carlisle
Feb 11 at 23:06