Macro char `#` doubles when made letter?

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP











up vote
8
down vote

favorite
1












If I make a command that takes one argument, and the argument should be allowed to take the unescaped default macro char: # as an argument and then treat it as a letter:



documentclassarticle
makeatletter
defallowhash#1
toks@#1catcode`#11relaxscantokensexpandafterthetoks@%

makeatother
begindocument
allowhashOne hash: #, not two
enddocument


The above works except it prints the following:
One hash: ##, not two. Is this expected behaviour? How could one fix this?










share|improve this question



















  • 1




    When the macro allowhash is called, the category code of # is still 6, so it is doubled when the argument is absorbed. With a later scantokens you get two # characters with category code 11 (12 would be a better choice). By the way, the group around the definition of allowhash is useless, because you're changing no category code at definition time.
    – egreg
    Sep 2 at 11:27











  • @egreg aha! Removed the grouping (it was just remains from when I was experimenting).
    – Andreas Storvik Strauman
    Sep 2 at 12:02














up vote
8
down vote

favorite
1












If I make a command that takes one argument, and the argument should be allowed to take the unescaped default macro char: # as an argument and then treat it as a letter:



documentclassarticle
makeatletter
defallowhash#1
toks@#1catcode`#11relaxscantokensexpandafterthetoks@%

makeatother
begindocument
allowhashOne hash: #, not two
enddocument


The above works except it prints the following:
One hash: ##, not two. Is this expected behaviour? How could one fix this?










share|improve this question



















  • 1




    When the macro allowhash is called, the category code of # is still 6, so it is doubled when the argument is absorbed. With a later scantokens you get two # characters with category code 11 (12 would be a better choice). By the way, the group around the definition of allowhash is useless, because you're changing no category code at definition time.
    – egreg
    Sep 2 at 11:27











  • @egreg aha! Removed the grouping (it was just remains from when I was experimenting).
    – Andreas Storvik Strauman
    Sep 2 at 12:02












up vote
8
down vote

favorite
1









up vote
8
down vote

favorite
1






1





If I make a command that takes one argument, and the argument should be allowed to take the unescaped default macro char: # as an argument and then treat it as a letter:



documentclassarticle
makeatletter
defallowhash#1
toks@#1catcode`#11relaxscantokensexpandafterthetoks@%

makeatother
begindocument
allowhashOne hash: #, not two
enddocument


The above works except it prints the following:
One hash: ##, not two. Is this expected behaviour? How could one fix this?










share|improve this question















If I make a command that takes one argument, and the argument should be allowed to take the unescaped default macro char: # as an argument and then treat it as a letter:



documentclassarticle
makeatletter
defallowhash#1
toks@#1catcode`#11relaxscantokensexpandafterthetoks@%

makeatother
begindocument
allowhashOne hash: #, not two
enddocument


The above works except it prints the following:
One hash: ##, not two. Is this expected behaviour? How could one fix this?







macros catcodes






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Sep 2 at 12:01

























asked Sep 2 at 11:11









Andreas Storvik Strauman

2,257418




2,257418







  • 1




    When the macro allowhash is called, the category code of # is still 6, so it is doubled when the argument is absorbed. With a later scantokens you get two # characters with category code 11 (12 would be a better choice). By the way, the group around the definition of allowhash is useless, because you're changing no category code at definition time.
    – egreg
    Sep 2 at 11:27











  • @egreg aha! Removed the grouping (it was just remains from when I was experimenting).
    – Andreas Storvik Strauman
    Sep 2 at 12:02












  • 1




    When the macro allowhash is called, the category code of # is still 6, so it is doubled when the argument is absorbed. With a later scantokens you get two # characters with category code 11 (12 would be a better choice). By the way, the group around the definition of allowhash is useless, because you're changing no category code at definition time.
    – egreg
    Sep 2 at 11:27











  • @egreg aha! Removed the grouping (it was just remains from when I was experimenting).
    – Andreas Storvik Strauman
    Sep 2 at 12:02







1




1




When the macro allowhash is called, the category code of # is still 6, so it is doubled when the argument is absorbed. With a later scantokens you get two # characters with category code 11 (12 would be a better choice). By the way, the group around the definition of allowhash is useless, because you're changing no category code at definition time.
– egreg
Sep 2 at 11:27





When the macro allowhash is called, the category code of # is still 6, so it is doubled when the argument is absorbed. With a later scantokens you get two # characters with category code 11 (12 would be a better choice). By the way, the group around the definition of allowhash is useless, because you're changing no category code at definition time.
– egreg
Sep 2 at 11:27













@egreg aha! Removed the grouping (it was just remains from when I was experimenting).
– Andreas Storvik Strauman
Sep 2 at 12:02




@egreg aha! Removed the grouping (it was just remains from when I was experimenting).
– Andreas Storvik Strauman
Sep 2 at 12:02










3 Answers
3






active

oldest

votes

















up vote
2
down vote



accepted










Let's look at your code:



documentclassarticle
makeatletter
defallowhash#1
toks@#1catcode`#11relaxscantokensexpandafterthetoks@%

makeatother
begindocument
allowhashOne hash: #, not two
enddocument


LaTeX does fetch an argument for allowhash, hereby tokenizing the tokens which form that argument under normal category-code-régime. Thus LaTeX does fetch an explicit-hash-character-token of category code 6(parameter) as a component of allowhash's argument.



When expanding allowhash, this token becomes a part of the content of toks@.



Thus toks@ contains an explicit-hash-character-token of category code 6(parameter).



Due to expandafter...the-trickery this explicit-hash-character-token of category code 6(parameter) ends up as a component of the ⟨general text⟩ of scantokens.



scantokens emulates unexpanded-writing the tokens from its ⟨general text⟩ into file and reading them back from the file and hereby tokenizing things under the current category-code-régime.



When writing to file or screen, explicit character tokens of category-code 6(parameter) get doubled.



Thus the hash gets doubled by scantokens unexpanded-writing-part.



Off the cuff I can only offer a routine ReplaceEveryHash which takes one argument and does replace each explicit catcode-6-character-token of the argument by its stringification—this mechanism does not act only on explicit catcode-6-hashes but on all explicit catcode-6-character-tokens.



documentclassarticle

makeatletter
%%=============================================================================
%% Paraphernalia:
%% UD@firstoftwo, UD@secondoftwo,
%% UD@PassFirstToSecond, UD@Exchange, UD@removespace
%% UD@CheckWhetherNull, UD@CheckWhetherBrace,
%% UD@CheckWhetherLeadingSpace, UD@ExtractFirstArg
%%=============================================================================
newcommandUD@firstoftwo[2]#1%
newcommandUD@secondoftwo[2]#2%
newcommandUD@PassFirstToSecond[2]#2#1%
newcommandUD@Exchange[2]#2#1%
newcommandUD@removespaceUD@firstoftwodefUD@removespace %
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% UD@CheckWhetherNull<Argument which is to be checked>%
%% <Tokens to be delivered in case that argument
%% which is to be checked is empty>%
%% <Tokens to be delivered in case that argument
%% which is to be checked is not empty>%
%%
%% The gist of this macro comes from Robert R. Schneck's ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
newcommandUD@CheckWhetherNull[1]%
romannumeral0expandafterUD@secondoftwostringexpandafter
UD@secondoftwoexpandafterexpandafterstring#1expandafter
UD@secondoftwostringexpandafterUD@firstoftwoexpandafterexpandafter
UD@secondoftwostringexpandafterexpandafterUD@firstoftwo %
UD@secondoftwoexpandafterexpandafterUD@firstoftwo UD@firstoftwo%
%
%%-----------------------------------------------------------------------------
%% Check whether argument's first token is a catcode-1-character
%%.............................................................................
%% UD@CheckWhetherBrace<Argument which is to be checked>%
%% <Tokens to be delivered in case that argument
%% which is to be checked has leading
%% catcode-1-token>%
%% <Tokens to be delivered in case that argument
%% which is to be checked has no leading
%% catcode-1-token>%
newcommandUD@CheckWhetherBrace[1]%
romannumeral0expandafterUD@secondoftwoexpandafterexpandafter%
string#1.expandafterUD@firstoftwoexpandafterexpandafter
UD@secondoftwostringexpandafterexpandafterUD@firstoftwo %
UD@firstoftwoexpandafterexpandafterUD@firstoftwo UD@secondoftwo%
%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% UD@CheckWhetherLeadingSpace<Argument which is to be checked>%
%% <Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is a
%% space-token>%
%% <Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is not
%% a space-token>%
newcommandUD@CheckWhetherLeadingSpace[1]%
romannumeral0UD@CheckWhetherNull#1%
expandafterexpandafterUD@firstoftwo UD@secondoftwo%
expandafterUD@secondoftwostringUD@CheckWhetherLeadingSpaceB.#1 %
%
newcommandUD@CheckWhetherLeadingSpaceB%
longdefUD@CheckWhetherLeadingSpaceB#1 %
expandafterUD@CheckWhetherNullexpandafterUD@secondoftwo#1%
UD@ExchangeUD@firstoftwoUD@ExchangeUD@secondoftwo%
UD@Exchange expandafterexpandafterexpandafterexpandafter
expandafterexpandafterexpandafterexpandafterexpandafter
expandafterexpandafterUD@secondoftwoexpandafterstring%
%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%
%% UD@ExtractFirstArgABCDE yields A
%%
%% UD@ExtractFirstArgABCDE yields AB
%%.............................................................................
newcommandUD@RemoveTillUD@SelDOm%
longdefUD@RemoveTillUD@SelDOm#1#2UD@SelDOm#1%
newcommandUD@ExtractFirstArg[1]%
romannumeral0%
UD@ExtractFirstArgLoop#1UD@SelDOm%
%
newcommandUD@ExtractFirstArgLoop[1]%
expandafterUD@CheckWhetherNullexpandafterUD@firstoftwo#1%
#1%
expandafterUD@ExtractFirstArgLoopexpandafterUD@RemoveTillUD@SelDOm#1%
%
%%=============================================================================
%% ReplaceEveryHash<argument>%
%%
%% Each explicit catcode-6(parameter)-character-token of the <argument>
%% will be replaced by its stringification.
%%
%% You obtain the result after two expansion-steps, i.e.,
%% in expansion-contexts you get the result after "hitting"
%% ReplaceEveryHash by two expandafter.
%%
%% As a side-effect, the routine does replace matching pairs of explicit
%% character tokens of catcode 1 and 2 by matching pairs of curly braces
%% of catcode 1 and 2.
%% I suppose this won't be a problem in most situations as usually the
%% curly braces are the only characters of category code 1 / 2...
%%
%% This routine needs detokenize from the eTeX extensions.
%%-----------------------------------------------------------------------------
newcommandReplaceEveryHash[1]%
romannumeral0UD@ReplaceEveryHashLoop#1%
%
newcommandUD@ReplaceEveryHashLoop[2]%
UD@CheckWhetherNull#1 #2%
UD@CheckWhetherLeadingSpace#1%
expandafterUD@ReplaceEveryHashLoop
expandafterUD@removespace#1#2 %
%
UD@CheckWhetherBrace#1%
expandafterexpandafterexpandafterUD@PassFirstToSecond
expandafterexpandafterexpandafter%
expandafterUD@PassFirstToSecondexpandafter%
romannumeral0expandafterUD@ReplaceEveryHashLoop
romannumeral0UD@ExtractFirstArgLoop#1UD@SelDOm%
#2%
expandafterUD@ReplaceEveryHashLoop
expandafterUD@firstoftwo#1%
%
expandafterUD@CheckWhetherHash
romannumeral0UD@ExtractFirstArgLoop#1UD@SelDOm#1#2%
%
%
%
%
newcommandUD@CheckWhetherHash[3]%
expandafterexpandafterexpandafterUD@CheckWhetherNull
expandafterexpandafterexpandafter%
expandafterUD@firstoftwo
expandafterexpandafterstring#1%
expandafterexpandafterexpandafterUD@CheckWhetherNull
expandafterexpandafterexpandafter%
expandafterUD@firstoftwo
expandafterexpandafterdetokenize#1%
expandafterUD@ReplaceEveryHashLoop
expandafterUD@firstoftwo#2#3#1%
%
expandafterexpandafterexpandafterUD@PassFirstToSecond
expandafterexpandafterexpandafterexpandafterUD@Exchange
expandafterstring#1#3%
expandafterUD@ReplaceEveryHashLoop
expandafterUD@firstoftwo#2%
%
%
%
expandafterUD@ReplaceEveryHashLoop
expandafterUD@firstoftwo#2#3#1%
%
%
%----------------------------------------------------------------------

newcommandallowhash[1]ReplaceEveryHash#1

newcommandallowanddetokenizehash[1]%
detokenizeexpandafterexpandafterexpandafterReplaceEveryHash#1%
%

makeatother

begindocument
allowhashOne hash: #, not two

begingroup
frenchspacing
ttfamily allowanddetokenizehashOne hash: #, not two. This time in braces:#####
endgroup

For comparison the effect of verb|detokenize| without prior hash-replacing:

begingroup
frenchspacing
ttfamily detokenizeOne hash: #, not two. This time in braces:#####
endgroup

enddocument


enter image description here






share|improve this answer





























    up vote
    10
    down vote













    enter image description here



    documentclassarticle
    makeatletter
    defallowhash%%%%
    bgroupeveryeofegroupcatcode35=11relaxscantokens%
    makeatother
    begindocument
    allowhashOne hash: #, not two

    enddocument


    or in a macro as requested in comments



    enter image description here



    documentclassarticle
    makeatletter
    defallowhash%%%%
    bgroupcatcode35=11relaxafterassignmentegroupgdeffoo%
    makeatother
    begindocument
    allowhashOne hash: #, not two

    [foo] [foo]
    enddocument





    share|improve this answer






















    • What if I wanted the result in a macro instead of just printing it?
      – Andreas Storvik Strauman
      Sep 2 at 11:35










    • @AndreasStorvikStrauman added a macro version
      – David Carlisle
      Sep 2 at 11:39










    • Could you add some words of explanation how the first macro works?
      – siracusa
      Sep 2 at 15:00











    • @siracusa it just makes # non-special without parsing the ... as an argument of allowhash, the scantokens isn't really needed at all except the end of file hook is used to insert an endgroup to restore the meaning of #
      – David Carlisle
      Sep 2 at 15:13

















    up vote
    4
    down vote













    When scantokens acts, the # characters are already doubled, because they're absorbed as the argument to a macro.



    With the l3regex module of expl3 it's easier:



    documentclassarticle
    usepackagexparse

    ExplSyntaxOn

    NewDocumentCommandallowhashm

    tl_set:Nn l_tmpa_tl #1
    regex_replace_all:nnN cP# cO# l_tmpa_tl
    tl_use:N l_tmpa_tl

    ExplSyntaxOff

    begindocument

    allowhashOne hash: #, not two

    enddocument


    enter image description here



    You can easily add support for saving the token list in a macro.



    documentclassarticle
    usepackagexparse

    ExplSyntaxOn

    NewDocumentCommandallowhashom

    tl_set:Nn l_tmpa_tl #2
    regex_replace_all:nnN cP# cO# l_tmpa_tl
    IfNoValueTF #1
    tl_use:N l_tmpa_tl
    tl_set_eq:NN #1 l_tmpa_tl


    ExplSyntaxOff

    begindocument

    allowhashOne hash: #, not two

    allowhash[foo]One hash: #, not two

    textttmeaningfoo

    enddocument


    enter image description here



    (There seems to be a double space in the picture, but it's only due to nonfrenchspacing.)






    share|improve this answer






















      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',
      convertImagesToLinks: false,
      noModals: false,
      showLowRepImageUploadWarning: true,
      reputationToPostImages: null,
      bindNavPrevention: true,
      postfix: "",
      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%2f448972%2fmacro-char-doubles-when-made-letter%23new-answer', 'question_page');

      );

      Post as a guest






























      3 Answers
      3






      active

      oldest

      votes








      3 Answers
      3






      active

      oldest

      votes









      active

      oldest

      votes






      active

      oldest

      votes








      up vote
      2
      down vote



      accepted










      Let's look at your code:



      documentclassarticle
      makeatletter
      defallowhash#1
      toks@#1catcode`#11relaxscantokensexpandafterthetoks@%

      makeatother
      begindocument
      allowhashOne hash: #, not two
      enddocument


      LaTeX does fetch an argument for allowhash, hereby tokenizing the tokens which form that argument under normal category-code-régime. Thus LaTeX does fetch an explicit-hash-character-token of category code 6(parameter) as a component of allowhash's argument.



      When expanding allowhash, this token becomes a part of the content of toks@.



      Thus toks@ contains an explicit-hash-character-token of category code 6(parameter).



      Due to expandafter...the-trickery this explicit-hash-character-token of category code 6(parameter) ends up as a component of the &langle;general text&rangle; of scantokens.



      scantokens emulates unexpanded-writing the tokens from its &langle;general text&rangle; into file and reading them back from the file and hereby tokenizing things under the current category-code-régime.



      When writing to file or screen, explicit character tokens of category-code 6(parameter) get doubled.



      Thus the hash gets doubled by scantokens unexpanded-writing-part.



      Off the cuff I can only offer a routine ReplaceEveryHash which takes one argument and does replace each explicit catcode-6-character-token of the argument by its stringification—this mechanism does not act only on explicit catcode-6-hashes but on all explicit catcode-6-character-tokens.



      documentclassarticle

      makeatletter
      %%=============================================================================
      %% Paraphernalia:
      %% UD@firstoftwo, UD@secondoftwo,
      %% UD@PassFirstToSecond, UD@Exchange, UD@removespace
      %% UD@CheckWhetherNull, UD@CheckWhetherBrace,
      %% UD@CheckWhetherLeadingSpace, UD@ExtractFirstArg
      %%=============================================================================
      newcommandUD@firstoftwo[2]#1%
      newcommandUD@secondoftwo[2]#2%
      newcommandUD@PassFirstToSecond[2]#2#1%
      newcommandUD@Exchange[2]#2#1%
      newcommandUD@removespaceUD@firstoftwodefUD@removespace %
      %%-----------------------------------------------------------------------------
      %% Check whether argument is empty:
      %%.............................................................................
      %% UD@CheckWhetherNull<Argument which is to be checked>%
      %% <Tokens to be delivered in case that argument
      %% which is to be checked is empty>%
      %% <Tokens to be delivered in case that argument
      %% which is to be checked is not empty>%
      %%
      %% The gist of this macro comes from Robert R. Schneck's ifempty-macro:
      %% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
      newcommandUD@CheckWhetherNull[1]%
      romannumeral0expandafterUD@secondoftwostringexpandafter
      UD@secondoftwoexpandafterexpandafterstring#1expandafter
      UD@secondoftwostringexpandafterUD@firstoftwoexpandafterexpandafter
      UD@secondoftwostringexpandafterexpandafterUD@firstoftwo %
      UD@secondoftwoexpandafterexpandafterUD@firstoftwo UD@firstoftwo%
      %
      %%-----------------------------------------------------------------------------
      %% Check whether argument's first token is a catcode-1-character
      %%.............................................................................
      %% UD@CheckWhetherBrace<Argument which is to be checked>%
      %% <Tokens to be delivered in case that argument
      %% which is to be checked has leading
      %% catcode-1-token>%
      %% <Tokens to be delivered in case that argument
      %% which is to be checked has no leading
      %% catcode-1-token>%
      newcommandUD@CheckWhetherBrace[1]%
      romannumeral0expandafterUD@secondoftwoexpandafterexpandafter%
      string#1.expandafterUD@firstoftwoexpandafterexpandafter
      UD@secondoftwostringexpandafterexpandafterUD@firstoftwo %
      UD@firstoftwoexpandafterexpandafterUD@firstoftwo UD@secondoftwo%
      %
      %%-----------------------------------------------------------------------------
      %% Check whether brace-balanced argument starts with a space-token
      %%.............................................................................
      %% UD@CheckWhetherLeadingSpace<Argument which is to be checked>%
      %% <Tokens to be delivered in case <argument
      %% which is to be checked>'s 1st token is a
      %% space-token>%
      %% <Tokens to be delivered in case <argument
      %% which is to be checked>'s 1st token is not
      %% a space-token>%
      newcommandUD@CheckWhetherLeadingSpace[1]%
      romannumeral0UD@CheckWhetherNull#1%
      expandafterexpandafterUD@firstoftwo UD@secondoftwo%
      expandafterUD@secondoftwostringUD@CheckWhetherLeadingSpaceB.#1 %
      %
      newcommandUD@CheckWhetherLeadingSpaceB%
      longdefUD@CheckWhetherLeadingSpaceB#1 %
      expandafterUD@CheckWhetherNullexpandafterUD@secondoftwo#1%
      UD@ExchangeUD@firstoftwoUD@ExchangeUD@secondoftwo%
      UD@Exchange expandafterexpandafterexpandafterexpandafter
      expandafterexpandafterexpandafterexpandafterexpandafter
      expandafterexpandafterUD@secondoftwoexpandafterstring%
      %
      %%-----------------------------------------------------------------------------
      %% Extract first inner undelimited argument:
      %%
      %% UD@ExtractFirstArgABCDE yields A
      %%
      %% UD@ExtractFirstArgABCDE yields AB
      %%.............................................................................
      newcommandUD@RemoveTillUD@SelDOm%
      longdefUD@RemoveTillUD@SelDOm#1#2UD@SelDOm#1%
      newcommandUD@ExtractFirstArg[1]%
      romannumeral0%
      UD@ExtractFirstArgLoop#1UD@SelDOm%
      %
      newcommandUD@ExtractFirstArgLoop[1]%
      expandafterUD@CheckWhetherNullexpandafterUD@firstoftwo#1%
      #1%
      expandafterUD@ExtractFirstArgLoopexpandafterUD@RemoveTillUD@SelDOm#1%
      %
      %%=============================================================================
      %% ReplaceEveryHash<argument>%
      %%
      %% Each explicit catcode-6(parameter)-character-token of the <argument>
      %% will be replaced by its stringification.
      %%
      %% You obtain the result after two expansion-steps, i.e.,
      %% in expansion-contexts you get the result after "hitting"
      %% ReplaceEveryHash by two expandafter.
      %%
      %% As a side-effect, the routine does replace matching pairs of explicit
      %% character tokens of catcode 1 and 2 by matching pairs of curly braces
      %% of catcode 1 and 2.
      %% I suppose this won't be a problem in most situations as usually the
      %% curly braces are the only characters of category code 1 / 2...
      %%
      %% This routine needs detokenize from the eTeX extensions.
      %%-----------------------------------------------------------------------------
      newcommandReplaceEveryHash[1]%
      romannumeral0UD@ReplaceEveryHashLoop#1%
      %
      newcommandUD@ReplaceEveryHashLoop[2]%
      UD@CheckWhetherNull#1 #2%
      UD@CheckWhetherLeadingSpace#1%
      expandafterUD@ReplaceEveryHashLoop
      expandafterUD@removespace#1#2 %
      %
      UD@CheckWhetherBrace#1%
      expandafterexpandafterexpandafterUD@PassFirstToSecond
      expandafterexpandafterexpandafter%
      expandafterUD@PassFirstToSecondexpandafter%
      romannumeral0expandafterUD@ReplaceEveryHashLoop
      romannumeral0UD@ExtractFirstArgLoop#1UD@SelDOm%
      #2%
      expandafterUD@ReplaceEveryHashLoop
      expandafterUD@firstoftwo#1%
      %
      expandafterUD@CheckWhetherHash
      romannumeral0UD@ExtractFirstArgLoop#1UD@SelDOm#1#2%
      %
      %
      %
      %
      newcommandUD@CheckWhetherHash[3]%
      expandafterexpandafterexpandafterUD@CheckWhetherNull
      expandafterexpandafterexpandafter%
      expandafterUD@firstoftwo
      expandafterexpandafterstring#1%
      expandafterexpandafterexpandafterUD@CheckWhetherNull
      expandafterexpandafterexpandafter%
      expandafterUD@firstoftwo
      expandafterexpandafterdetokenize#1%
      expandafterUD@ReplaceEveryHashLoop
      expandafterUD@firstoftwo#2#3#1%
      %
      expandafterexpandafterexpandafterUD@PassFirstToSecond
      expandafterexpandafterexpandafterexpandafterUD@Exchange
      expandafterstring#1#3%
      expandafterUD@ReplaceEveryHashLoop
      expandafterUD@firstoftwo#2%
      %
      %
      %
      expandafterUD@ReplaceEveryHashLoop
      expandafterUD@firstoftwo#2#3#1%
      %
      %
      %----------------------------------------------------------------------

      newcommandallowhash[1]ReplaceEveryHash#1

      newcommandallowanddetokenizehash[1]%
      detokenizeexpandafterexpandafterexpandafterReplaceEveryHash#1%
      %

      makeatother

      begindocument
      allowhashOne hash: #, not two

      begingroup
      frenchspacing
      ttfamily allowanddetokenizehashOne hash: #, not two. This time in braces:#####
      endgroup

      For comparison the effect of verb|detokenize| without prior hash-replacing:

      begingroup
      frenchspacing
      ttfamily detokenizeOne hash: #, not two. This time in braces:#####
      endgroup

      enddocument


      enter image description here






      share|improve this answer


























        up vote
        2
        down vote



        accepted










        Let's look at your code:



        documentclassarticle
        makeatletter
        defallowhash#1
        toks@#1catcode`#11relaxscantokensexpandafterthetoks@%

        makeatother
        begindocument
        allowhashOne hash: #, not two
        enddocument


        LaTeX does fetch an argument for allowhash, hereby tokenizing the tokens which form that argument under normal category-code-régime. Thus LaTeX does fetch an explicit-hash-character-token of category code 6(parameter) as a component of allowhash's argument.



        When expanding allowhash, this token becomes a part of the content of toks@.



        Thus toks@ contains an explicit-hash-character-token of category code 6(parameter).



        Due to expandafter...the-trickery this explicit-hash-character-token of category code 6(parameter) ends up as a component of the &langle;general text&rangle; of scantokens.



        scantokens emulates unexpanded-writing the tokens from its &langle;general text&rangle; into file and reading them back from the file and hereby tokenizing things under the current category-code-régime.



        When writing to file or screen, explicit character tokens of category-code 6(parameter) get doubled.



        Thus the hash gets doubled by scantokens unexpanded-writing-part.



        Off the cuff I can only offer a routine ReplaceEveryHash which takes one argument and does replace each explicit catcode-6-character-token of the argument by its stringification—this mechanism does not act only on explicit catcode-6-hashes but on all explicit catcode-6-character-tokens.



        documentclassarticle

        makeatletter
        %%=============================================================================
        %% Paraphernalia:
        %% UD@firstoftwo, UD@secondoftwo,
        %% UD@PassFirstToSecond, UD@Exchange, UD@removespace
        %% UD@CheckWhetherNull, UD@CheckWhetherBrace,
        %% UD@CheckWhetherLeadingSpace, UD@ExtractFirstArg
        %%=============================================================================
        newcommandUD@firstoftwo[2]#1%
        newcommandUD@secondoftwo[2]#2%
        newcommandUD@PassFirstToSecond[2]#2#1%
        newcommandUD@Exchange[2]#2#1%
        newcommandUD@removespaceUD@firstoftwodefUD@removespace %
        %%-----------------------------------------------------------------------------
        %% Check whether argument is empty:
        %%.............................................................................
        %% UD@CheckWhetherNull<Argument which is to be checked>%
        %% <Tokens to be delivered in case that argument
        %% which is to be checked is empty>%
        %% <Tokens to be delivered in case that argument
        %% which is to be checked is not empty>%
        %%
        %% The gist of this macro comes from Robert R. Schneck's ifempty-macro:
        %% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
        newcommandUD@CheckWhetherNull[1]%
        romannumeral0expandafterUD@secondoftwostringexpandafter
        UD@secondoftwoexpandafterexpandafterstring#1expandafter
        UD@secondoftwostringexpandafterUD@firstoftwoexpandafterexpandafter
        UD@secondoftwostringexpandafterexpandafterUD@firstoftwo %
        UD@secondoftwoexpandafterexpandafterUD@firstoftwo UD@firstoftwo%
        %
        %%-----------------------------------------------------------------------------
        %% Check whether argument's first token is a catcode-1-character
        %%.............................................................................
        %% UD@CheckWhetherBrace<Argument which is to be checked>%
        %% <Tokens to be delivered in case that argument
        %% which is to be checked has leading
        %% catcode-1-token>%
        %% <Tokens to be delivered in case that argument
        %% which is to be checked has no leading
        %% catcode-1-token>%
        newcommandUD@CheckWhetherBrace[1]%
        romannumeral0expandafterUD@secondoftwoexpandafterexpandafter%
        string#1.expandafterUD@firstoftwoexpandafterexpandafter
        UD@secondoftwostringexpandafterexpandafterUD@firstoftwo %
        UD@firstoftwoexpandafterexpandafterUD@firstoftwo UD@secondoftwo%
        %
        %%-----------------------------------------------------------------------------
        %% Check whether brace-balanced argument starts with a space-token
        %%.............................................................................
        %% UD@CheckWhetherLeadingSpace<Argument which is to be checked>%
        %% <Tokens to be delivered in case <argument
        %% which is to be checked>'s 1st token is a
        %% space-token>%
        %% <Tokens to be delivered in case <argument
        %% which is to be checked>'s 1st token is not
        %% a space-token>%
        newcommandUD@CheckWhetherLeadingSpace[1]%
        romannumeral0UD@CheckWhetherNull#1%
        expandafterexpandafterUD@firstoftwo UD@secondoftwo%
        expandafterUD@secondoftwostringUD@CheckWhetherLeadingSpaceB.#1 %
        %
        newcommandUD@CheckWhetherLeadingSpaceB%
        longdefUD@CheckWhetherLeadingSpaceB#1 %
        expandafterUD@CheckWhetherNullexpandafterUD@secondoftwo#1%
        UD@ExchangeUD@firstoftwoUD@ExchangeUD@secondoftwo%
        UD@Exchange expandafterexpandafterexpandafterexpandafter
        expandafterexpandafterexpandafterexpandafterexpandafter
        expandafterexpandafterUD@secondoftwoexpandafterstring%
        %
        %%-----------------------------------------------------------------------------
        %% Extract first inner undelimited argument:
        %%
        %% UD@ExtractFirstArgABCDE yields A
        %%
        %% UD@ExtractFirstArgABCDE yields AB
        %%.............................................................................
        newcommandUD@RemoveTillUD@SelDOm%
        longdefUD@RemoveTillUD@SelDOm#1#2UD@SelDOm#1%
        newcommandUD@ExtractFirstArg[1]%
        romannumeral0%
        UD@ExtractFirstArgLoop#1UD@SelDOm%
        %
        newcommandUD@ExtractFirstArgLoop[1]%
        expandafterUD@CheckWhetherNullexpandafterUD@firstoftwo#1%
        #1%
        expandafterUD@ExtractFirstArgLoopexpandafterUD@RemoveTillUD@SelDOm#1%
        %
        %%=============================================================================
        %% ReplaceEveryHash<argument>%
        %%
        %% Each explicit catcode-6(parameter)-character-token of the <argument>
        %% will be replaced by its stringification.
        %%
        %% You obtain the result after two expansion-steps, i.e.,
        %% in expansion-contexts you get the result after "hitting"
        %% ReplaceEveryHash by two expandafter.
        %%
        %% As a side-effect, the routine does replace matching pairs of explicit
        %% character tokens of catcode 1 and 2 by matching pairs of curly braces
        %% of catcode 1 and 2.
        %% I suppose this won't be a problem in most situations as usually the
        %% curly braces are the only characters of category code 1 / 2...
        %%
        %% This routine needs detokenize from the eTeX extensions.
        %%-----------------------------------------------------------------------------
        newcommandReplaceEveryHash[1]%
        romannumeral0UD@ReplaceEveryHashLoop#1%
        %
        newcommandUD@ReplaceEveryHashLoop[2]%
        UD@CheckWhetherNull#1 #2%
        UD@CheckWhetherLeadingSpace#1%
        expandafterUD@ReplaceEveryHashLoop
        expandafterUD@removespace#1#2 %
        %
        UD@CheckWhetherBrace#1%
        expandafterexpandafterexpandafterUD@PassFirstToSecond
        expandafterexpandafterexpandafter%
        expandafterUD@PassFirstToSecondexpandafter%
        romannumeral0expandafterUD@ReplaceEveryHashLoop
        romannumeral0UD@ExtractFirstArgLoop#1UD@SelDOm%
        #2%
        expandafterUD@ReplaceEveryHashLoop
        expandafterUD@firstoftwo#1%
        %
        expandafterUD@CheckWhetherHash
        romannumeral0UD@ExtractFirstArgLoop#1UD@SelDOm#1#2%
        %
        %
        %
        %
        newcommandUD@CheckWhetherHash[3]%
        expandafterexpandafterexpandafterUD@CheckWhetherNull
        expandafterexpandafterexpandafter%
        expandafterUD@firstoftwo
        expandafterexpandafterstring#1%
        expandafterexpandafterexpandafterUD@CheckWhetherNull
        expandafterexpandafterexpandafter%
        expandafterUD@firstoftwo
        expandafterexpandafterdetokenize#1%
        expandafterUD@ReplaceEveryHashLoop
        expandafterUD@firstoftwo#2#3#1%
        %
        expandafterexpandafterexpandafterUD@PassFirstToSecond
        expandafterexpandafterexpandafterexpandafterUD@Exchange
        expandafterstring#1#3%
        expandafterUD@ReplaceEveryHashLoop
        expandafterUD@firstoftwo#2%
        %
        %
        %
        expandafterUD@ReplaceEveryHashLoop
        expandafterUD@firstoftwo#2#3#1%
        %
        %
        %----------------------------------------------------------------------

        newcommandallowhash[1]ReplaceEveryHash#1

        newcommandallowanddetokenizehash[1]%
        detokenizeexpandafterexpandafterexpandafterReplaceEveryHash#1%
        %

        makeatother

        begindocument
        allowhashOne hash: #, not two

        begingroup
        frenchspacing
        ttfamily allowanddetokenizehashOne hash: #, not two. This time in braces:#####
        endgroup

        For comparison the effect of verb|detokenize| without prior hash-replacing:

        begingroup
        frenchspacing
        ttfamily detokenizeOne hash: #, not two. This time in braces:#####
        endgroup

        enddocument


        enter image description here






        share|improve this answer
























          up vote
          2
          down vote



          accepted







          up vote
          2
          down vote



          accepted






          Let's look at your code:



          documentclassarticle
          makeatletter
          defallowhash#1
          toks@#1catcode`#11relaxscantokensexpandafterthetoks@%

          makeatother
          begindocument
          allowhashOne hash: #, not two
          enddocument


          LaTeX does fetch an argument for allowhash, hereby tokenizing the tokens which form that argument under normal category-code-régime. Thus LaTeX does fetch an explicit-hash-character-token of category code 6(parameter) as a component of allowhash's argument.



          When expanding allowhash, this token becomes a part of the content of toks@.



          Thus toks@ contains an explicit-hash-character-token of category code 6(parameter).



          Due to expandafter...the-trickery this explicit-hash-character-token of category code 6(parameter) ends up as a component of the &langle;general text&rangle; of scantokens.



          scantokens emulates unexpanded-writing the tokens from its &langle;general text&rangle; into file and reading them back from the file and hereby tokenizing things under the current category-code-régime.



          When writing to file or screen, explicit character tokens of category-code 6(parameter) get doubled.



          Thus the hash gets doubled by scantokens unexpanded-writing-part.



          Off the cuff I can only offer a routine ReplaceEveryHash which takes one argument and does replace each explicit catcode-6-character-token of the argument by its stringification—this mechanism does not act only on explicit catcode-6-hashes but on all explicit catcode-6-character-tokens.



          documentclassarticle

          makeatletter
          %%=============================================================================
          %% Paraphernalia:
          %% UD@firstoftwo, UD@secondoftwo,
          %% UD@PassFirstToSecond, UD@Exchange, UD@removespace
          %% UD@CheckWhetherNull, UD@CheckWhetherBrace,
          %% UD@CheckWhetherLeadingSpace, UD@ExtractFirstArg
          %%=============================================================================
          newcommandUD@firstoftwo[2]#1%
          newcommandUD@secondoftwo[2]#2%
          newcommandUD@PassFirstToSecond[2]#2#1%
          newcommandUD@Exchange[2]#2#1%
          newcommandUD@removespaceUD@firstoftwodefUD@removespace %
          %%-----------------------------------------------------------------------------
          %% Check whether argument is empty:
          %%.............................................................................
          %% UD@CheckWhetherNull<Argument which is to be checked>%
          %% <Tokens to be delivered in case that argument
          %% which is to be checked is empty>%
          %% <Tokens to be delivered in case that argument
          %% which is to be checked is not empty>%
          %%
          %% The gist of this macro comes from Robert R. Schneck's ifempty-macro:
          %% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
          newcommandUD@CheckWhetherNull[1]%
          romannumeral0expandafterUD@secondoftwostringexpandafter
          UD@secondoftwoexpandafterexpandafterstring#1expandafter
          UD@secondoftwostringexpandafterUD@firstoftwoexpandafterexpandafter
          UD@secondoftwostringexpandafterexpandafterUD@firstoftwo %
          UD@secondoftwoexpandafterexpandafterUD@firstoftwo UD@firstoftwo%
          %
          %%-----------------------------------------------------------------------------
          %% Check whether argument's first token is a catcode-1-character
          %%.............................................................................
          %% UD@CheckWhetherBrace<Argument which is to be checked>%
          %% <Tokens to be delivered in case that argument
          %% which is to be checked has leading
          %% catcode-1-token>%
          %% <Tokens to be delivered in case that argument
          %% which is to be checked has no leading
          %% catcode-1-token>%
          newcommandUD@CheckWhetherBrace[1]%
          romannumeral0expandafterUD@secondoftwoexpandafterexpandafter%
          string#1.expandafterUD@firstoftwoexpandafterexpandafter
          UD@secondoftwostringexpandafterexpandafterUD@firstoftwo %
          UD@firstoftwoexpandafterexpandafterUD@firstoftwo UD@secondoftwo%
          %
          %%-----------------------------------------------------------------------------
          %% Check whether brace-balanced argument starts with a space-token
          %%.............................................................................
          %% UD@CheckWhetherLeadingSpace<Argument which is to be checked>%
          %% <Tokens to be delivered in case <argument
          %% which is to be checked>'s 1st token is a
          %% space-token>%
          %% <Tokens to be delivered in case <argument
          %% which is to be checked>'s 1st token is not
          %% a space-token>%
          newcommandUD@CheckWhetherLeadingSpace[1]%
          romannumeral0UD@CheckWhetherNull#1%
          expandafterexpandafterUD@firstoftwo UD@secondoftwo%
          expandafterUD@secondoftwostringUD@CheckWhetherLeadingSpaceB.#1 %
          %
          newcommandUD@CheckWhetherLeadingSpaceB%
          longdefUD@CheckWhetherLeadingSpaceB#1 %
          expandafterUD@CheckWhetherNullexpandafterUD@secondoftwo#1%
          UD@ExchangeUD@firstoftwoUD@ExchangeUD@secondoftwo%
          UD@Exchange expandafterexpandafterexpandafterexpandafter
          expandafterexpandafterexpandafterexpandafterexpandafter
          expandafterexpandafterUD@secondoftwoexpandafterstring%
          %
          %%-----------------------------------------------------------------------------
          %% Extract first inner undelimited argument:
          %%
          %% UD@ExtractFirstArgABCDE yields A
          %%
          %% UD@ExtractFirstArgABCDE yields AB
          %%.............................................................................
          newcommandUD@RemoveTillUD@SelDOm%
          longdefUD@RemoveTillUD@SelDOm#1#2UD@SelDOm#1%
          newcommandUD@ExtractFirstArg[1]%
          romannumeral0%
          UD@ExtractFirstArgLoop#1UD@SelDOm%
          %
          newcommandUD@ExtractFirstArgLoop[1]%
          expandafterUD@CheckWhetherNullexpandafterUD@firstoftwo#1%
          #1%
          expandafterUD@ExtractFirstArgLoopexpandafterUD@RemoveTillUD@SelDOm#1%
          %
          %%=============================================================================
          %% ReplaceEveryHash<argument>%
          %%
          %% Each explicit catcode-6(parameter)-character-token of the <argument>
          %% will be replaced by its stringification.
          %%
          %% You obtain the result after two expansion-steps, i.e.,
          %% in expansion-contexts you get the result after "hitting"
          %% ReplaceEveryHash by two expandafter.
          %%
          %% As a side-effect, the routine does replace matching pairs of explicit
          %% character tokens of catcode 1 and 2 by matching pairs of curly braces
          %% of catcode 1 and 2.
          %% I suppose this won't be a problem in most situations as usually the
          %% curly braces are the only characters of category code 1 / 2...
          %%
          %% This routine needs detokenize from the eTeX extensions.
          %%-----------------------------------------------------------------------------
          newcommandReplaceEveryHash[1]%
          romannumeral0UD@ReplaceEveryHashLoop#1%
          %
          newcommandUD@ReplaceEveryHashLoop[2]%
          UD@CheckWhetherNull#1 #2%
          UD@CheckWhetherLeadingSpace#1%
          expandafterUD@ReplaceEveryHashLoop
          expandafterUD@removespace#1#2 %
          %
          UD@CheckWhetherBrace#1%
          expandafterexpandafterexpandafterUD@PassFirstToSecond
          expandafterexpandafterexpandafter%
          expandafterUD@PassFirstToSecondexpandafter%
          romannumeral0expandafterUD@ReplaceEveryHashLoop
          romannumeral0UD@ExtractFirstArgLoop#1UD@SelDOm%
          #2%
          expandafterUD@ReplaceEveryHashLoop
          expandafterUD@firstoftwo#1%
          %
          expandafterUD@CheckWhetherHash
          romannumeral0UD@ExtractFirstArgLoop#1UD@SelDOm#1#2%
          %
          %
          %
          %
          newcommandUD@CheckWhetherHash[3]%
          expandafterexpandafterexpandafterUD@CheckWhetherNull
          expandafterexpandafterexpandafter%
          expandafterUD@firstoftwo
          expandafterexpandafterstring#1%
          expandafterexpandafterexpandafterUD@CheckWhetherNull
          expandafterexpandafterexpandafter%
          expandafterUD@firstoftwo
          expandafterexpandafterdetokenize#1%
          expandafterUD@ReplaceEveryHashLoop
          expandafterUD@firstoftwo#2#3#1%
          %
          expandafterexpandafterexpandafterUD@PassFirstToSecond
          expandafterexpandafterexpandafterexpandafterUD@Exchange
          expandafterstring#1#3%
          expandafterUD@ReplaceEveryHashLoop
          expandafterUD@firstoftwo#2%
          %
          %
          %
          expandafterUD@ReplaceEveryHashLoop
          expandafterUD@firstoftwo#2#3#1%
          %
          %
          %----------------------------------------------------------------------

          newcommandallowhash[1]ReplaceEveryHash#1

          newcommandallowanddetokenizehash[1]%
          detokenizeexpandafterexpandafterexpandafterReplaceEveryHash#1%
          %

          makeatother

          begindocument
          allowhashOne hash: #, not two

          begingroup
          frenchspacing
          ttfamily allowanddetokenizehashOne hash: #, not two. This time in braces:#####
          endgroup

          For comparison the effect of verb|detokenize| without prior hash-replacing:

          begingroup
          frenchspacing
          ttfamily detokenizeOne hash: #, not two. This time in braces:#####
          endgroup

          enddocument


          enter image description here






          share|improve this answer














          Let's look at your code:



          documentclassarticle
          makeatletter
          defallowhash#1
          toks@#1catcode`#11relaxscantokensexpandafterthetoks@%

          makeatother
          begindocument
          allowhashOne hash: #, not two
          enddocument


          LaTeX does fetch an argument for allowhash, hereby tokenizing the tokens which form that argument under normal category-code-régime. Thus LaTeX does fetch an explicit-hash-character-token of category code 6(parameter) as a component of allowhash's argument.



          When expanding allowhash, this token becomes a part of the content of toks@.



          Thus toks@ contains an explicit-hash-character-token of category code 6(parameter).



          Due to expandafter...the-trickery this explicit-hash-character-token of category code 6(parameter) ends up as a component of the &langle;general text&rangle; of scantokens.



          scantokens emulates unexpanded-writing the tokens from its &langle;general text&rangle; into file and reading them back from the file and hereby tokenizing things under the current category-code-régime.



          When writing to file or screen, explicit character tokens of category-code 6(parameter) get doubled.



          Thus the hash gets doubled by scantokens unexpanded-writing-part.



          Off the cuff I can only offer a routine ReplaceEveryHash which takes one argument and does replace each explicit catcode-6-character-token of the argument by its stringification—this mechanism does not act only on explicit catcode-6-hashes but on all explicit catcode-6-character-tokens.



          documentclassarticle

          makeatletter
          %%=============================================================================
          %% Paraphernalia:
          %% UD@firstoftwo, UD@secondoftwo,
          %% UD@PassFirstToSecond, UD@Exchange, UD@removespace
          %% UD@CheckWhetherNull, UD@CheckWhetherBrace,
          %% UD@CheckWhetherLeadingSpace, UD@ExtractFirstArg
          %%=============================================================================
          newcommandUD@firstoftwo[2]#1%
          newcommandUD@secondoftwo[2]#2%
          newcommandUD@PassFirstToSecond[2]#2#1%
          newcommandUD@Exchange[2]#2#1%
          newcommandUD@removespaceUD@firstoftwodefUD@removespace %
          %%-----------------------------------------------------------------------------
          %% Check whether argument is empty:
          %%.............................................................................
          %% UD@CheckWhetherNull<Argument which is to be checked>%
          %% <Tokens to be delivered in case that argument
          %% which is to be checked is empty>%
          %% <Tokens to be delivered in case that argument
          %% which is to be checked is not empty>%
          %%
          %% The gist of this macro comes from Robert R. Schneck's ifempty-macro:
          %% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
          newcommandUD@CheckWhetherNull[1]%
          romannumeral0expandafterUD@secondoftwostringexpandafter
          UD@secondoftwoexpandafterexpandafterstring#1expandafter
          UD@secondoftwostringexpandafterUD@firstoftwoexpandafterexpandafter
          UD@secondoftwostringexpandafterexpandafterUD@firstoftwo %
          UD@secondoftwoexpandafterexpandafterUD@firstoftwo UD@firstoftwo%
          %
          %%-----------------------------------------------------------------------------
          %% Check whether argument's first token is a catcode-1-character
          %%.............................................................................
          %% UD@CheckWhetherBrace<Argument which is to be checked>%
          %% <Tokens to be delivered in case that argument
          %% which is to be checked has leading
          %% catcode-1-token>%
          %% <Tokens to be delivered in case that argument
          %% which is to be checked has no leading
          %% catcode-1-token>%
          newcommandUD@CheckWhetherBrace[1]%
          romannumeral0expandafterUD@secondoftwoexpandafterexpandafter%
          string#1.expandafterUD@firstoftwoexpandafterexpandafter
          UD@secondoftwostringexpandafterexpandafterUD@firstoftwo %
          UD@firstoftwoexpandafterexpandafterUD@firstoftwo UD@secondoftwo%
          %
          %%-----------------------------------------------------------------------------
          %% Check whether brace-balanced argument starts with a space-token
          %%.............................................................................
          %% UD@CheckWhetherLeadingSpace<Argument which is to be checked>%
          %% <Tokens to be delivered in case <argument
          %% which is to be checked>'s 1st token is a
          %% space-token>%
          %% <Tokens to be delivered in case <argument
          %% which is to be checked>'s 1st token is not
          %% a space-token>%
          newcommandUD@CheckWhetherLeadingSpace[1]%
          romannumeral0UD@CheckWhetherNull#1%
          expandafterexpandafterUD@firstoftwo UD@secondoftwo%
          expandafterUD@secondoftwostringUD@CheckWhetherLeadingSpaceB.#1 %
          %
          newcommandUD@CheckWhetherLeadingSpaceB%
          longdefUD@CheckWhetherLeadingSpaceB#1 %
          expandafterUD@CheckWhetherNullexpandafterUD@secondoftwo#1%
          UD@ExchangeUD@firstoftwoUD@ExchangeUD@secondoftwo%
          UD@Exchange expandafterexpandafterexpandafterexpandafter
          expandafterexpandafterexpandafterexpandafterexpandafter
          expandafterexpandafterUD@secondoftwoexpandafterstring%
          %
          %%-----------------------------------------------------------------------------
          %% Extract first inner undelimited argument:
          %%
          %% UD@ExtractFirstArgABCDE yields A
          %%
          %% UD@ExtractFirstArgABCDE yields AB
          %%.............................................................................
          newcommandUD@RemoveTillUD@SelDOm%
          longdefUD@RemoveTillUD@SelDOm#1#2UD@SelDOm#1%
          newcommandUD@ExtractFirstArg[1]%
          romannumeral0%
          UD@ExtractFirstArgLoop#1UD@SelDOm%
          %
          newcommandUD@ExtractFirstArgLoop[1]%
          expandafterUD@CheckWhetherNullexpandafterUD@firstoftwo#1%
          #1%
          expandafterUD@ExtractFirstArgLoopexpandafterUD@RemoveTillUD@SelDOm#1%
          %
          %%=============================================================================
          %% ReplaceEveryHash<argument>%
          %%
          %% Each explicit catcode-6(parameter)-character-token of the <argument>
          %% will be replaced by its stringification.
          %%
          %% You obtain the result after two expansion-steps, i.e.,
          %% in expansion-contexts you get the result after "hitting"
          %% ReplaceEveryHash by two expandafter.
          %%
          %% As a side-effect, the routine does replace matching pairs of explicit
          %% character tokens of catcode 1 and 2 by matching pairs of curly braces
          %% of catcode 1 and 2.
          %% I suppose this won't be a problem in most situations as usually the
          %% curly braces are the only characters of category code 1 / 2...
          %%
          %% This routine needs detokenize from the eTeX extensions.
          %%-----------------------------------------------------------------------------
          newcommandReplaceEveryHash[1]%
          romannumeral0UD@ReplaceEveryHashLoop#1%
          %
          newcommandUD@ReplaceEveryHashLoop[2]%
          UD@CheckWhetherNull#1 #2%
          UD@CheckWhetherLeadingSpace#1%
          expandafterUD@ReplaceEveryHashLoop
          expandafterUD@removespace#1#2 %
          %
          UD@CheckWhetherBrace#1%
          expandafterexpandafterexpandafterUD@PassFirstToSecond
          expandafterexpandafterexpandafter%
          expandafterUD@PassFirstToSecondexpandafter%
          romannumeral0expandafterUD@ReplaceEveryHashLoop
          romannumeral0UD@ExtractFirstArgLoop#1UD@SelDOm%
          #2%
          expandafterUD@ReplaceEveryHashLoop
          expandafterUD@firstoftwo#1%
          %
          expandafterUD@CheckWhetherHash
          romannumeral0UD@ExtractFirstArgLoop#1UD@SelDOm#1#2%
          %
          %
          %
          %
          newcommandUD@CheckWhetherHash[3]%
          expandafterexpandafterexpandafterUD@CheckWhetherNull
          expandafterexpandafterexpandafter%
          expandafterUD@firstoftwo
          expandafterexpandafterstring#1%
          expandafterexpandafterexpandafterUD@CheckWhetherNull
          expandafterexpandafterexpandafter%
          expandafterUD@firstoftwo
          expandafterexpandafterdetokenize#1%
          expandafterUD@ReplaceEveryHashLoop
          expandafterUD@firstoftwo#2#3#1%
          %
          expandafterexpandafterexpandafterUD@PassFirstToSecond
          expandafterexpandafterexpandafterexpandafterUD@Exchange
          expandafterstring#1#3%
          expandafterUD@ReplaceEveryHashLoop
          expandafterUD@firstoftwo#2%
          %
          %
          %
          expandafterUD@ReplaceEveryHashLoop
          expandafterUD@firstoftwo#2#3#1%
          %
          %
          %----------------------------------------------------------------------

          newcommandallowhash[1]ReplaceEveryHash#1

          newcommandallowanddetokenizehash[1]%
          detokenizeexpandafterexpandafterexpandafterReplaceEveryHash#1%
          %

          makeatother

          begindocument
          allowhashOne hash: #, not two

          begingroup
          frenchspacing
          ttfamily allowanddetokenizehashOne hash: #, not two. This time in braces:#####
          endgroup

          For comparison the effect of verb|detokenize| without prior hash-replacing:

          begingroup
          frenchspacing
          ttfamily detokenizeOne hash: #, not two. This time in braces:#####
          endgroup

          enddocument


          enter image description here







          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited Sep 3 at 11:11









          Phelype Oleinik

          16.4k33567




          16.4k33567










          answered Sep 2 at 21:29









          Ulrich Diez

          3,350414




          3,350414




















              up vote
              10
              down vote













              enter image description here



              documentclassarticle
              makeatletter
              defallowhash%%%%
              bgroupeveryeofegroupcatcode35=11relaxscantokens%
              makeatother
              begindocument
              allowhashOne hash: #, not two

              enddocument


              or in a macro as requested in comments



              enter image description here



              documentclassarticle
              makeatletter
              defallowhash%%%%
              bgroupcatcode35=11relaxafterassignmentegroupgdeffoo%
              makeatother
              begindocument
              allowhashOne hash: #, not two

              [foo] [foo]
              enddocument





              share|improve this answer






















              • What if I wanted the result in a macro instead of just printing it?
                – Andreas Storvik Strauman
                Sep 2 at 11:35










              • @AndreasStorvikStrauman added a macro version
                – David Carlisle
                Sep 2 at 11:39










              • Could you add some words of explanation how the first macro works?
                – siracusa
                Sep 2 at 15:00











              • @siracusa it just makes # non-special without parsing the ... as an argument of allowhash, the scantokens isn't really needed at all except the end of file hook is used to insert an endgroup to restore the meaning of #
                – David Carlisle
                Sep 2 at 15:13














              up vote
              10
              down vote













              enter image description here



              documentclassarticle
              makeatletter
              defallowhash%%%%
              bgroupeveryeofegroupcatcode35=11relaxscantokens%
              makeatother
              begindocument
              allowhashOne hash: #, not two

              enddocument


              or in a macro as requested in comments



              enter image description here



              documentclassarticle
              makeatletter
              defallowhash%%%%
              bgroupcatcode35=11relaxafterassignmentegroupgdeffoo%
              makeatother
              begindocument
              allowhashOne hash: #, not two

              [foo] [foo]
              enddocument





              share|improve this answer






















              • What if I wanted the result in a macro instead of just printing it?
                – Andreas Storvik Strauman
                Sep 2 at 11:35










              • @AndreasStorvikStrauman added a macro version
                – David Carlisle
                Sep 2 at 11:39










              • Could you add some words of explanation how the first macro works?
                – siracusa
                Sep 2 at 15:00











              • @siracusa it just makes # non-special without parsing the ... as an argument of allowhash, the scantokens isn't really needed at all except the end of file hook is used to insert an endgroup to restore the meaning of #
                – David Carlisle
                Sep 2 at 15:13












              up vote
              10
              down vote










              up vote
              10
              down vote









              enter image description here



              documentclassarticle
              makeatletter
              defallowhash%%%%
              bgroupeveryeofegroupcatcode35=11relaxscantokens%
              makeatother
              begindocument
              allowhashOne hash: #, not two

              enddocument


              or in a macro as requested in comments



              enter image description here



              documentclassarticle
              makeatletter
              defallowhash%%%%
              bgroupcatcode35=11relaxafterassignmentegroupgdeffoo%
              makeatother
              begindocument
              allowhashOne hash: #, not two

              [foo] [foo]
              enddocument





              share|improve this answer














              enter image description here



              documentclassarticle
              makeatletter
              defallowhash%%%%
              bgroupeveryeofegroupcatcode35=11relaxscantokens%
              makeatother
              begindocument
              allowhashOne hash: #, not two

              enddocument


              or in a macro as requested in comments



              enter image description here



              documentclassarticle
              makeatletter
              defallowhash%%%%
              bgroupcatcode35=11relaxafterassignmentegroupgdeffoo%
              makeatother
              begindocument
              allowhashOne hash: #, not two

              [foo] [foo]
              enddocument






              share|improve this answer














              share|improve this answer



              share|improve this answer








              edited Sep 2 at 11:39

























              answered Sep 2 at 11:24









              David Carlisle

              467k3810951818




              467k3810951818











              • What if I wanted the result in a macro instead of just printing it?
                – Andreas Storvik Strauman
                Sep 2 at 11:35










              • @AndreasStorvikStrauman added a macro version
                – David Carlisle
                Sep 2 at 11:39










              • Could you add some words of explanation how the first macro works?
                – siracusa
                Sep 2 at 15:00











              • @siracusa it just makes # non-special without parsing the ... as an argument of allowhash, the scantokens isn't really needed at all except the end of file hook is used to insert an endgroup to restore the meaning of #
                – David Carlisle
                Sep 2 at 15:13
















              • What if I wanted the result in a macro instead of just printing it?
                – Andreas Storvik Strauman
                Sep 2 at 11:35










              • @AndreasStorvikStrauman added a macro version
                – David Carlisle
                Sep 2 at 11:39










              • Could you add some words of explanation how the first macro works?
                – siracusa
                Sep 2 at 15:00











              • @siracusa it just makes # non-special without parsing the ... as an argument of allowhash, the scantokens isn't really needed at all except the end of file hook is used to insert an endgroup to restore the meaning of #
                – David Carlisle
                Sep 2 at 15:13















              What if I wanted the result in a macro instead of just printing it?
              – Andreas Storvik Strauman
              Sep 2 at 11:35




              What if I wanted the result in a macro instead of just printing it?
              – Andreas Storvik Strauman
              Sep 2 at 11:35












              @AndreasStorvikStrauman added a macro version
              – David Carlisle
              Sep 2 at 11:39




              @AndreasStorvikStrauman added a macro version
              – David Carlisle
              Sep 2 at 11:39












              Could you add some words of explanation how the first macro works?
              – siracusa
              Sep 2 at 15:00





              Could you add some words of explanation how the first macro works?
              – siracusa
              Sep 2 at 15:00













              @siracusa it just makes # non-special without parsing the ... as an argument of allowhash, the scantokens isn't really needed at all except the end of file hook is used to insert an endgroup to restore the meaning of #
              – David Carlisle
              Sep 2 at 15:13




              @siracusa it just makes # non-special without parsing the ... as an argument of allowhash, the scantokens isn't really needed at all except the end of file hook is used to insert an endgroup to restore the meaning of #
              – David Carlisle
              Sep 2 at 15:13










              up vote
              4
              down vote













              When scantokens acts, the # characters are already doubled, because they're absorbed as the argument to a macro.



              With the l3regex module of expl3 it's easier:



              documentclassarticle
              usepackagexparse

              ExplSyntaxOn

              NewDocumentCommandallowhashm

              tl_set:Nn l_tmpa_tl #1
              regex_replace_all:nnN cP# cO# l_tmpa_tl
              tl_use:N l_tmpa_tl

              ExplSyntaxOff

              begindocument

              allowhashOne hash: #, not two

              enddocument


              enter image description here



              You can easily add support for saving the token list in a macro.



              documentclassarticle
              usepackagexparse

              ExplSyntaxOn

              NewDocumentCommandallowhashom

              tl_set:Nn l_tmpa_tl #2
              regex_replace_all:nnN cP# cO# l_tmpa_tl
              IfNoValueTF #1
              tl_use:N l_tmpa_tl
              tl_set_eq:NN #1 l_tmpa_tl


              ExplSyntaxOff

              begindocument

              allowhashOne hash: #, not two

              allowhash[foo]One hash: #, not two

              textttmeaningfoo

              enddocument


              enter image description here



              (There seems to be a double space in the picture, but it's only due to nonfrenchspacing.)






              share|improve this answer


























                up vote
                4
                down vote













                When scantokens acts, the # characters are already doubled, because they're absorbed as the argument to a macro.



                With the l3regex module of expl3 it's easier:



                documentclassarticle
                usepackagexparse

                ExplSyntaxOn

                NewDocumentCommandallowhashm

                tl_set:Nn l_tmpa_tl #1
                regex_replace_all:nnN cP# cO# l_tmpa_tl
                tl_use:N l_tmpa_tl

                ExplSyntaxOff

                begindocument

                allowhashOne hash: #, not two

                enddocument


                enter image description here



                You can easily add support for saving the token list in a macro.



                documentclassarticle
                usepackagexparse

                ExplSyntaxOn

                NewDocumentCommandallowhashom

                tl_set:Nn l_tmpa_tl #2
                regex_replace_all:nnN cP# cO# l_tmpa_tl
                IfNoValueTF #1
                tl_use:N l_tmpa_tl
                tl_set_eq:NN #1 l_tmpa_tl


                ExplSyntaxOff

                begindocument

                allowhashOne hash: #, not two

                allowhash[foo]One hash: #, not two

                textttmeaningfoo

                enddocument


                enter image description here



                (There seems to be a double space in the picture, but it's only due to nonfrenchspacing.)






                share|improve this answer
























                  up vote
                  4
                  down vote










                  up vote
                  4
                  down vote









                  When scantokens acts, the # characters are already doubled, because they're absorbed as the argument to a macro.



                  With the l3regex module of expl3 it's easier:



                  documentclassarticle
                  usepackagexparse

                  ExplSyntaxOn

                  NewDocumentCommandallowhashm

                  tl_set:Nn l_tmpa_tl #1
                  regex_replace_all:nnN cP# cO# l_tmpa_tl
                  tl_use:N l_tmpa_tl

                  ExplSyntaxOff

                  begindocument

                  allowhashOne hash: #, not two

                  enddocument


                  enter image description here



                  You can easily add support for saving the token list in a macro.



                  documentclassarticle
                  usepackagexparse

                  ExplSyntaxOn

                  NewDocumentCommandallowhashom

                  tl_set:Nn l_tmpa_tl #2
                  regex_replace_all:nnN cP# cO# l_tmpa_tl
                  IfNoValueTF #1
                  tl_use:N l_tmpa_tl
                  tl_set_eq:NN #1 l_tmpa_tl


                  ExplSyntaxOff

                  begindocument

                  allowhashOne hash: #, not two

                  allowhash[foo]One hash: #, not two

                  textttmeaningfoo

                  enddocument


                  enter image description here



                  (There seems to be a double space in the picture, but it's only due to nonfrenchspacing.)






                  share|improve this answer














                  When scantokens acts, the # characters are already doubled, because they're absorbed as the argument to a macro.



                  With the l3regex module of expl3 it's easier:



                  documentclassarticle
                  usepackagexparse

                  ExplSyntaxOn

                  NewDocumentCommandallowhashm

                  tl_set:Nn l_tmpa_tl #1
                  regex_replace_all:nnN cP# cO# l_tmpa_tl
                  tl_use:N l_tmpa_tl

                  ExplSyntaxOff

                  begindocument

                  allowhashOne hash: #, not two

                  enddocument


                  enter image description here



                  You can easily add support for saving the token list in a macro.



                  documentclassarticle
                  usepackagexparse

                  ExplSyntaxOn

                  NewDocumentCommandallowhashom

                  tl_set:Nn l_tmpa_tl #2
                  regex_replace_all:nnN cP# cO# l_tmpa_tl
                  IfNoValueTF #1
                  tl_use:N l_tmpa_tl
                  tl_set_eq:NN #1 l_tmpa_tl


                  ExplSyntaxOff

                  begindocument

                  allowhashOne hash: #, not two

                  allowhash[foo]One hash: #, not two

                  textttmeaningfoo

                  enddocument


                  enter image description here



                  (There seems to be a double space in the picture, but it's only due to nonfrenchspacing.)







                  share|improve this answer














                  share|improve this answer



                  share|improve this answer








                  edited Sep 2 at 11:46

























                  answered Sep 2 at 11:41









                  egreg

                  682k8318133061




                  682k8318133061



























                       

                      draft saved


                      draft discarded















































                       


                      draft saved


                      draft discarded














                      StackExchange.ready(
                      function ()
                      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2ftex.stackexchange.com%2fquestions%2f448972%2fmacro-char-doubles-when-made-letter%23new-answer', 'question_page');

                      );

                      Post as a guest













































































                      這個網誌中的熱門文章

                      How to combine Bézier curves to a surface?

                      Carbon dioxide

                      Why am i infinitely getting the same tweet with the Twitter Search API?