2

I want to define a macro that behaves like \ttfamily, i.e. it should read everything until } (or \endgroup?) as a parameter.

I.e. this

{\mynewmacro lorem ipsum
 dolores
}

should be equivalent to this

\mymacro{lorem ipsum
 dolores
}

Is this possible?

Volker
  • 347
  • your two requests are somewhat incompatible, \ttfamily does not read ahead to the end of the group, it is not equivalent to a macro with argument in the way you suggest with \mymacro – David Carlisle Jan 22 '18 at 22:28
  • 4
    Perhaps it would be better to show your use-case of \mymacro. – Werner Jan 22 '18 at 22:31
  • 2
    This looks like an “X-Y problem” (that is, you actually want to do Y, and you find out that you could do it, if only you were able to do X—which, unfortunately, you are not; so you ask how to do X, but what you actually want to achieve is Y). If so, please tell us what Y is: perhaps, we could suggest a different way to achieve it that doesn’t require going through X. – GuM Jan 22 '18 at 23:55
  • @GuM: The issue I actually have is that when using \texttt you sometimes get overfull boxes, because hyphenation doesn't work properly. That's a known issue. I want to insert a \linebreak[1] after every character so as mentioned here: https://tex.stackexchange.com/questions/324042. I want a new command that replaces \texttt and ideally I also want a command that replaces \ttfamily to have that functionality. But it appears that \ttfamily is special in that sense. – Volker Jan 23 '18 at 07:17
  • 1
    @Volker There are so many places where such a \mynewmacro could go wrong that's better to give up with it. Since you want to process text in a special way, a command with argument is what you need. – egreg Jan 23 '18 at 07:40
  • @Volker This would be like asking \newmbox so you can say {\newmbox abc} instead of \mbox{abc}. It doesn't make sense. – egreg Jan 23 '18 at 07:47
  • @egreg: I understand, thanks for your input. I'll try to avoid it. – Volker Jan 23 '18 at 08:04
  • @Volker it isn't that "hyphenation doesn't work properly" it is that hyphenation is deliberately disabled, it is trivial to define a tt font without hyphenation being disabled. A fragile parsing ahead for a close of group is completely the wrong way to attack this. Especially in latex where end of group is more likely to be \end{foo} than } a syntax such as you suggest is never going to work. – David Carlisle Jan 23 '18 at 08:58
  • @DavidCarlisle I find this highly non trivial. Suppose I want to define a new column type so I can typeset an entire table in texttt. Here I'd use \ttfamily. How would I also have the feature that long words don't run into the next cell or the document margin? I don't care about proper hyphenation because the words may not be english words, but hashes or other random strings of characters. – Volker Jan 23 '18 at 13:52
  • for that you don't really want hyphenation no point in trying to match words in natural language to get correct hyphenation, there are answers here about allowing long strings to break, I'll see if I can find.... – David Carlisle Jan 23 '18 at 14:19
  • also of course neither egreg's nor manuel's solution would work for table cells were the group ends with & or \\ rather than } – David Carlisle Jan 23 '18 at 14:20
  • https://tex.stackexchange.com/questions/324042/linebreaks-in-long-character-strings/324048#324048 – David Carlisle Jan 23 '18 at 14:22
  • to apply the macro with argument to a table cell use (say) makecell package – David Carlisle Jan 23 '18 at 14:23

2 Answers2

6

The macro \ttfamily does not read up to }: it is a declaration that sets (locally) the current font and its action continues until the current group ends.

You can do this, but it's conceptually wrong to begin with.

\long\def\mymacro#1{--#1--}
\long\def\mynewmacro{\egroup\iftrue\expandafter\mymacro\expandafter{\else}\fi}

\mymacro{lorem ipsum
 dolores
}

{\mynewmacro lorem ipsum
 dolores
}

\bye

The \egroup balances the initial {; then we have the problem to make \mymacro into seeing an explicit { and to get rid of the necessary } (because \def wouldn't accept an unbalanced list of tokens). The \iftrue test follows the true branch; actually what it does is simply disappearing, leaving

\expandafter\mymacro\expandafter{\else}\fi

on the input stream, but with TeX knowing it should ignore the \else part. With \expandafter we reach this \else, whose expansion removes everything up to \fi. So at the end we remain with

\mymacro{\fi

so by general rule \mymacro will read up to the matching }. The remaining \fi will expand leaving nothing at all.

Note, however, that you can't call this as \begingroup\mynewmacro ...\endgroup nor use \bgroup and \egroup.

egreg
  • 1,121,712
  • 1
    \begingroup \mynewmacro hello egreg\endgroup – David Carlisle Jan 22 '18 at 22:37
  • 1
    @DavidCarlisle The OP wanted braces. ;-) – egreg Jan 22 '18 at 22:38
  • Why also fail with \bgroup \mynewmacro hello egreg \egroup? – Fran Jan 22 '18 at 22:48
  • Could you elaborate on the code a bit? The \iftrue trick looks interesting. – Daniel Jan 22 '18 at 22:50
  • 1
    @Fran basically your bgroup example fails as you asked that it acts like a macro with arguments and you can not (for any macro taking an argument) use \mymacro \bgroup ...\egroup the argument of the macro is just \bgroup not the whole group. – David Carlisle Jan 22 '18 at 22:54
  • 3
    A classic alternative, if I remember correctly, \def\mynewmacro{\aftergroup\mymacro\aftergroup{}}. – Manuel Jan 22 '18 at 23:00
  • @Manuel Yes, it's a possible alternative. – egreg Jan 22 '18 at 23:04
  • 1
    @Manuel: Why don’t you turn your comment into an answer? – GuM Jan 22 '18 at 23:50
  • @Manual: Great! I like yours better because it's so short. And it appears to be just what I need. If this is bad practice somehow I'm open to learn why. – Volker Jan 23 '18 at 07:23
  • @GuM It's the same idea than this answer: close the opened group, and then leave \mymacro{ in there. egreg solution does that by putting }\mymacro{} and removing with a trick the last } before expanding \mymacro, and what this does is write \mymacro{} and moving with a trick the two tokens \mymacro and { to after the group closes, thus effectively leaving }\mymacro{. – Manuel Jan 23 '18 at 08:37
  • @Volker You can use it, but I don't see a reason to write a different answer, I'll leave it in the comments. – Manuel Jan 23 '18 at 08:38
  • @Manuel: Yes, both solutions were already clear to me :-) I was just suggesting that yours deserves some upvotes too! I actually prefer the \aftergroup trick, it’s both more elegant and more efficient, and this, IMHO, is the reason for writing a different answer. – GuM Jan 27 '18 at 00:10
0

With the following example

  • the macro \FetchTillCloseBrace{\macro} fetches everything till the next unmatched closing brace and passes that as undelimited argument to \macro, re-inserting that unmatched closing brace.

  • the macro \CopyBehindCloseBrace is an example of how to put/copy tokens from inside a pair of braces forming a local scope behind the closing brace of that scope.

Be aware that processing via macros/macro arguments breaks things that rely on reading and tokenizing things under different catcode-régime, e.g., \verb and the like.

\documentclass{article}

\makeatletter
%%----------------------------------------------------------------------
%% 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>
%%
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral0\expandafter\@secondoftwo\string{\expandafter
  \@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \@secondoftwo\string}\expandafter\@firstoftwo\expandafter{\expandafter
  \@secondoftwo\string}\expandafter\expandafter\@firstoftwo{ }{}%
  \@secondoftwo}{\expandafter\expandafter\@firstoftwo{ }{}\@firstoftwo}%
}%
%%----------------------------------------------------------------------
%% \UD@PullOutFirstUndelimitedArgument{<action>}%
%%                                    {{<e_k>}{<e_(k+1)>}..{<e_n>}}%
%% yields
%%  <action>{<e_k>}{{<e_(k+1)>}..{<e_n>}}
%% It must be ensured that list is not empty!
%%......................................................................
\newcommand\UD@Exchange[2]{#2#1}%
\newcommand\UD@KeepFirstTillSelDOM{}%
\long\def\UD@KeepFirstTillSelDOM#1#2\UD@SelDOM{{#1}}%
\newcommand\UD@PullOutFirstUndelimitedArgument[2]{%
    \expandafter\UD@Exchange
    \expandafter{%
    \expandafter{%
    \@firstoftwo{}#2}}{\UD@ExtractFirstListElementLoop{#2\UD@SelDOM}{#1}}%
}%
\newcommand\UD@ExtractFirstListElementLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\@firstoftwo{}#1}%
  {\UD@Exchange{#1}}%
  {%
    \expandafter\UD@ExtractFirstListElementLoop
    \expandafter{%
    \UD@KeepFirstTillSelDOM#1}%
  }%
}%
%%----------------------------------------------------------------------
%% \CopyBehindCloseBrace
%%
%%   {<token sequence A>\CopyBehindCloseBrace{<tokens to appear only behind closing brace}<tokens to be copied behind closing brace>}
%% yields:
%%   {<token sequence A><tokens to be copied behind closing brace>}<tokens to appear only behind closing brace>{<tokens to be copied behind closing brace>}
%%......................................................................
\newcommand\FetchTillCloseBraceStart[2]{%
  \expandafter\expandafter\expandafter\UD@PullOutFirstUndelimitedArgument
  \expandafter\expandafter\expandafter#1%
  \expandafter\expandafter\expandafter{%
  \expandafter\@secondoftwo\string}{}%
  {#2}%
}%
\newcommand\CopyBehindCloseBrace{%
  \romannumeral0%
  \FetchTillCloseBraceStart{\@CopyBehindCloseBrace}%
}%
\newcommand\@CopyBehindCloseBrace[2]{%
   \expandafter\@secondoftwo\string{{ }#2}#1{#2}%
}%
%%----------------------------------------------------------------------
%% \FetchTillCloseBrace{\macro}
%%
%%   {<token sequence A>\FetchTillCloseBrace{\macro}<remaining tokens in brace group>}
%% yields:
%%   {<token sequence A>\macro{<remaining tokens in brace group>}}
%%......................................................................
\newcommand\FetchTillCloseBrace{%
  \romannumeral0%
  \FetchTillCloseBraceStart{\@PassToMacroInsideScope}%
}%
\newcommand\@PassToMacroInsideScope[2]{%
   \expandafter\@secondoftwo\string{{ }#1{#2}}%
}%
%%----------------------------------------------------------------------
%%
%% {ABC \LowercaseTillCloseBrace DEF}
%%   -> {ABC \lowercase{DEF}}
%%
%%......................................................................
\newcommand\LowercaseTillCloseBrace{%
   \FetchTillCloseBrace{\lowercase}%
}%

\makeatother

\begin{document}

\frenchspacing

{This is rmfamily. \FetchTillCloseBrace{\textsf}This is sffamily.}
This is rmfamily.

\bigskip

{ABC \LowercaseTillCloseBrace DEF}
GHI

\bigskip

{%<--This starts the boldface-group
 \bfseries This is boldface.
 \CopyBehindCloseBrace{This is to appear only outside the boldface-group. \textit}%
 This is to be copied outside the boldface-group.
}%<--This ends the boldface-group

\bigskip

\def\tempa{This is tempa outside the local scope. }%
{%
  \def\tempa{This is tempa inside the local scope. }%

  \CopyBehindCloseBrace{}\tempa
}

% This does _not(!) work:
% {Text\CopyBehindCloseBrace{Bla}\verb$verbatim text?$}
%
% Nor does this work:
%
% {Text \FetchTillCloseBrace{\textbf}\verb&verbatim \TeX t&}
%
% While this works:
% {Text \bfseries\selectfont \verb&verbatim \TeX t&}

\end{document}

enter image description here

Ulrich Diez
  • 28,770