How do you write a macro that takes arguments containing paragraphs?
Clash Royale CLAN TAG#URR8PPP
I'm trying to write a macro that takes arguments containing paragraphs. If you write a normal macro and one of its arguments contains a paragraph, it will break:
documentclassarticle
usepackage[utf8]inputenc
titletest
begindocument
maketitle
sectionIntroduction
defmymacro#1#1
mymacroThis
contains a paragraph
enddocument
This produces an error:
Runaway argument?
show 9 more comments
At the time TeX was written, one page of a document would take several minutes to be processed, and syntax highlighting was not a thing, so it was a good thing to have some mechanism to detect if you forgot a . A
def
, by default, doesn't allow a par
token unless you explicitly say it's a longdef
:
defmymacro#1#1
LaTeX, on the other hand, uses that by default, so if you use proper LaTeX commands (def
shouldn't be used in LaTeX documents), newcommand
makes a longdef
by default. If you want a “short” def
then you use newcommand*
.
xparse
returns the short argument default, but lets you define a long
macro using the +
argument modifier:
NewDocumentCommandmymacro m#1% def
NewDocumentCommandmymacro+m#1% longdef
Your second attempt is clever, and it could have worked except for two things.
First is that you are using gdefoldparpar
and then gdefparoldpar
. Once you expand par
you get oldpar
which, when expanded, yields par
which, when expanded, yields oldpar
which, when expanded, yields par
which, when expanded, yields oldpar
... Running forever :/
You need to use let
(or globallet
to have global effect) in this case: letoldparpar
. This creates an exact copy of par
named oldpar
which does not depend on what is par
.
Second, the runaway argument checking is implemented in a lower level, independent of the definition of par
, so this would fail with the same error:
letparrelax
defmymacro#1#1
mymacrofoo
bar
because when TeX sees two endlinechar
tokens (which is a space by default) TeX inserts an implicit par
token, which raises the Runaway argument
error. Knowing that, then:
newcountoldELchar
oldELchar=endlinechar
defmymacroendlinechar=-1relaxmymacroi
defmymacroi#1#1endlinechar=oldELchar
mymacrofoo
bar
won't raise an error, but a new line won't be a space anymore.
At the time TeX was written, one page of a document would take several minutes to be processed, and syntax highlighting was not a thing, so it was a good thing to have some mechanism to detect if you forgot a }
. A def
, by default, doesn't allow a par
token unless you explicitly say it's a longdef
:
defmymacro#1#1
LaTeX, on the other hand, uses that by default, so if you use proper LaTeX commands (def
shouldn't be used in LaTeX documents), newcommand
makes a longdef
by default. If you want a “short” def
then you use newcommand*
.
xparse
returns the short argument default, but lets you define a long
macro using the +
argument modifier:
NewDocumentCommandmymacro m#1% def
NewDocumentCommandmymacro+m#1% longdef
Your second attempt is clever, and it could have worked except for two things.
First is that you are using gdefoldparpar
and then gdefparoldpar
. Once you expand par
you get oldpar
which, when expanded, yields par
which, when expanded, yields oldpar
which, when expanded, yields par
which, when expanded, yields oldpar
... Running forever :/
You need to use let
(or globallet
to have global effect) in this case: letoldparpar
. This creates an exact copy of par
named oldpar
which does not depend on what is par
.
Second, the runaway argument checking is implemented in a lower level, independent of the definition of par
, so this would fail with the same error:
letparrelax
defmymacro#1#1
mymacrofoo
bar
because when TeX sees two endlinechar
tokens (which is a space by default) TeX inserts an implicit par
token, which raises the Runaway argument
error. Knowing that, then:
newcountoldELchar
oldELchar=endlinechar
defmymacroendlinechar=-1relaxmymacroi
defmymacroi#1#1endlinechar=oldELchar
mymacrofoo
bar
won't raise an error, but a new line won't be a space anymore.
answered Mar 7 at 13:32
Phelype OleinikPhelype Oleinik
24.9k54690
24.9k54690
|
show 9 more comments
Haha, I was wondering where I went into infinite recursion...
– sgf
Mar 7 at 13:36
@sgf You can say, for instance,tracingall
before the command you suspect is infinite-looping. You'll get a ton of garbage in the terminal and the log, but it gets easier to find out what is TeX doing.
– Phelype Oleinik
Mar 7 at 13:39
I would take a slight difference with the statement "(def
shouldn't be used in LaTeX documents)". Certain service routines a user writes may require the special parsing provided bydef
syntax. I think it would be better to say "(def
should be avoided, where possible)".
– Steven B. Segletes
Mar 7 at 13:44
1
the endlinechar version has to be used with care, for example it also removes the end of line aftermymacro
(even if there is text followingmymacro
) not just the one in the argument.
– David Carlisle
Mar 7 at 13:47
1
tex takes a line at a time and inserts the character specified by endlinechar at the point it grabs the next line at the end of the buffer before tokenizing., so its timing is well defined but er "delicate" :-)
– David Carlisle
Mar 7 at 13:59
Haha, I was wondering where I went into infinite recursion...
– sgf
Mar 7 at 13:36
Haha, I was wondering where I went into infinite recursion...
– sgf
Mar 7 at 13:36
@sgf You can say, for instance,
tracingall
before the command you suspect is infinite-looping. You'll get a ton of garbage in the terminal and the log, but it gets easier to find out what is TeX doing.– Phelype Oleinik
Mar 7 at 13:39
@sgf You can say, for instance,
tracingall
before the command you suspect is infinite-looping. You'll get a ton of garbage in the terminal and the log, but it gets easier to find out what is TeX doing.– Phelype Oleinik
Mar 7 at 13:39
I would take a slight difference with the statement "(
def
shouldn't be used in LaTeX documents)". Certain service routines a user writes may require the special parsing provided by def
syntax. I think it would be better to say "(def
should be avoided, where possible)".– Steven B. Segletes
Mar 7 at 13:44
I would take a slight difference with the statement "(
def
shouldn't be used in LaTeX documents)". Certain service routines a user writes may require the special parsing provided by def
syntax. I think it would be better to say "(def
should be avoided, where possible)".– Steven B. Segletes
Mar 7 at 13:44
1
1
the endlinechar version has to be used with care, for example it also removes the end of line after
mymacro
(even if there is text following mymacro
) not just the one in the argument.– David Carlisle
Mar 7 at 13:47
the endlinechar version has to be used with care, for example it also removes the end of line after
mymacro
(even if there is text following mymacro
) not just the one in the argument.– David Carlisle
Mar 7 at 13:47
1
1
tex takes a line at a time and inserts the character specified by endlinechar at the point it grabs the next line at the end of the buffer before tokenizing., so its timing is well defined but er "delicate" :-)
– David Carlisle
Mar 7 at 13:59
tex takes a line at a time and inserts the character specified by endlinechar at the point it grabs the next line at the end of the buffer before tokenizing., so its timing is well defined but er "delicate" :-)
– David Carlisle
Mar 7 at 13:59
|
show 9 more comments
The argument of a macro defined with def
does not allow par
tokens. Neither it allows blank lines, because they're transformed to par
during the phase in which TeX processes text input into tokens. Note that redefining par
is useless in this respect, because it's precisely the token par
that's disallowed, independently of its meaning.
Solution: make your macro long
.
longdefmymacro#1#1
Better solution:
newcommandmymacro[1]#1
because newcommand
uses longdef
internally. The variant newcommand*
instead uses def
without the prefix.
add a comment |
The argument of a macro defined with def
does not allow par
tokens. Neither it allows blank lines, because they're transformed to par
during the phase in which TeX processes text input into tokens. Note that redefining par
is useless in this respect, because it's precisely the token par
that's disallowed, independently of its meaning.
Solution: make your macro long
.
longdefmymacro#1#1
Better solution:
newcommandmymacro[1]#1
because newcommand
uses longdef
internally. The variant newcommand*
instead uses def
without the prefix.
add a comment |
The argument of a macro defined with def
does not allow par
tokens. Neither it allows blank lines, because they're transformed to par
during the phase in which TeX processes text input into tokens. Note that redefining par
is useless in this respect, because it's precisely the token par
that's disallowed, independently of its meaning.
Solution: make your macro long
.
longdefmymacro#1#1
Better solution:
newcommandmymacro[1]#1
because newcommand
uses longdef
internally. The variant newcommand*
instead uses def
without the prefix.
The argument of a macro defined with def
does not allow par
tokens. Neither it allows blank lines, because they're transformed to par
during the phase in which TeX processes text input into tokens. Note that redefining par
is useless in this respect, because it's precisely the token par
that's disallowed, independently of its meaning.
Solution: make your macro long
.
longdefmymacro#1#1
Better solution:
newcommandmymacro[1]#1
because newcommand
uses longdef
internally. The variant newcommand*
instead uses def
without the prefix.
answered Mar 7 at 21:17
egregegreg
731k8919303252
731k8919303252
add a comment |
add a comment |
Thanks for contributing an answer to TeX - LaTeX Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2ftex.stackexchange.com%2fquestions%2f478189%2fhow-do-you-write-a-macro-that-takes-arguments-containing-paragraphs%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Haha, I was wondering where I went into infinite recursion...
– sgf
Mar 7 at 13:36
@sgf You can say, for instance,
tracingall
before the command you suspect is infinite-looping. You'll get a ton of garbage in the terminal and the log, but it gets easier to find out what is TeX doing.– Phelype Oleinik
Mar 7 at 13:39
I would take a slight difference with the statement "(
def
shouldn't be used in LaTeX documents)". Certain service routines a user writes may require the special parsing provided bydef
syntax. I think it would be better to say "(def
should be avoided, where possible)".– Steven B. Segletes
Mar 7 at 13:44
1
the endlinechar version has to be used with care, for example it also removes the end of line after
mymacro
(even if there is text followingmymacro
) not just the one in the argument.– David Carlisle
Mar 7 at 13:47
1
tex takes a line at a time and inserts the character specified by endlinechar at the point it grabs the next line at the end of the buffer before tokenizing., so its timing is well defined but er "delicate" :-)
– David Carlisle
Mar 7 at 13:59