Different behavior if no arguments












3















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.










share|improve this question

























  • 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






  • 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 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


















3















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.










share|improve this question

























  • 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






  • 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 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
















3












3








3








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.










share|improve this question
















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






share|improve this question















share|improve this question













share|improve this question




share|improve this question








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 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






  • 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 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





















  • 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






  • 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 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



















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












3 Answers
3






active

oldest

votes


















6














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]





share|improve this answer





















  • 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





















3














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}





share|improve this answer

































    2














    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 token reserved@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}


      enter image description here




    • 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}


      enter image description here




    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 on futurelet) 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}


    enter image description here



    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. Thus XXX 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 within label-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&blank;{Something}



      Note the space character between XXX and {Something}.








    share|improve this answer


























    • 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











    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
    });


    }
    });














    draft saved

    draft discarded


















    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









    6














    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]





    share|improve this answer





















    • 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


















    6














    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]





    share|improve this answer





















    • 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
















    6












    6








    6







    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]





    share|improve this answer















    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]






    share|improve this answer














    share|improve this answer



    share|improve this answer








    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 have XXX 1 being the same as XXX {1} as they are conflicting requirements.

      – David Carlisle
      Feb 11 at 23:03
















    • 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










    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













    3














    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}





    share|improve this answer






























      3














      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}





      share|improve this answer




























        3












        3








        3







        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}





        share|improve this answer















        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}






        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Feb 12 at 11:08

























        answered Feb 12 at 2:13









        Ulrich DiezUlrich Diez

        4,865618




        4,865618























            2














            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{&langle;char&rangle;}{The next thing is &langle;char&rangle;}{The next thing is not &langle;char&rangle;}@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 token reserved@d, i.e., when you have something like



              kernel@ifnextchar{&langle;char&rangle;}{The next thing is &langle;char&rangle;}{The next thing is not &langle;char&rangle;}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}


              enter image description here




            • 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}


              enter image description here




            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 on futurelet) 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}


            enter image description here



            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. Thus XXX 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 within label-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&blank;{Something}



              Note the space character between XXX and {Something}.








            share|improve this answer


























            • 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
















            2














            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{&langle;char&rangle;}{The next thing is &langle;char&rangle;}{The next thing is not &langle;char&rangle;}@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 token reserved@d, i.e., when you have something like



              kernel@ifnextchar{&langle;char&rangle;}{The next thing is &langle;char&rangle;}{The next thing is not &langle;char&rangle;}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}


              enter image description here




            • 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}


              enter image description here




            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 on futurelet) 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}


            enter image description here



            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. Thus XXX 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 within label-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&blank;{Something}



              Note the space character between XXX and {Something}.








            share|improve this answer


























            • 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














            2












            2








            2







            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{&langle;char&rangle;}{The next thing is &langle;char&rangle;}{The next thing is not &langle;char&rangle;}@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 token reserved@d, i.e., when you have something like



              kernel@ifnextchar{&langle;char&rangle;}{The next thing is &langle;char&rangle;}{The next thing is not &langle;char&rangle;}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}


              enter image description here




            • 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}


              enter image description here




            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 on futurelet) 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}


            enter image description here



            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. Thus XXX 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 within label-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&blank;{Something}



              Note the space character between XXX and {Something}.








            share|improve this answer















            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{&langle;char&rangle;}{The next thing is &langle;char&rangle;}{The next thing is not &langle;char&rangle;}@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 token reserved@d, i.e., when you have something like



              kernel@ifnextchar{&langle;char&rangle;}{The next thing is &langle;char&rangle;}{The next thing is not &langle;char&rangle;}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}


              enter image description here




            • 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}


              enter image description here




            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 on futurelet) 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}


            enter image description here



            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. Thus XXX 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 within label-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&blank;{Something}



              Note the space character between XXX and {Something}.









            share|improve this answer














            share|improve this answer



            share|improve this answer








            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



















            • 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


















            draft saved

            draft discarded




















































            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.




            draft saved


            draft discarded














            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





















































            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







            Popular posts from this blog

            mysqli_query(): Empty query in /home/lucindabrummitt/public_html/blog/wp-includes/wp-db.php on line 1924

            How to change which sound is reproduced for terminal bell?

            Can I use Tabulator js library in my java Spring + Thymeleaf project?