bug-m4
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Problem: ifelse ifelse define conbination


From: Eric Blake
Subject: Re: Problem: ifelse ifelse define conbination
Date: Thu, 9 Feb 2017 11:24:55 -0600
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.7.0

On 02/09/2017 06:48 AM, Jan Jansen wrote:
> Please find attached to this E-mail the M4 source file, TestIfelseDefine.m4, 
> that is completely documented and should explain itself.

Pasting the text inline is a bit easier than via an attachment, but I'll
manage.  Also, your mail client did not wrap long lines, which makes it
harder to reply.

> The idea is to make a define() or perhaps better redefine command dependent 
> on some condition and use an ifelse(...,...,define(),define()) statement for 
> that and demonstrate and document that it works.

Yes, that should be doable.

> As the conditions became more complex, I decided to use a 
> ifelse(...,...,ifelse(...,...,define())) statement for that and it did not 
> work.

I'll examine your script below, and point out where you went wrong
(assuming that the error was indeed in your usage).

> My question too you is, am I miss using M4, it is trying to use it for 
> something that it is not supposed to do?

M4 is Turing complete, so it can do anything - but not always as
efficiently as some other languages, and not always with the same idioms.

> As for as I know the C-preprocessor allows such constructs, even deeply 
> nested and therefore I assumed M4 would do that too.

The C preprocessor has slightly different rules about rescanning, but it
is generally possible to rewrite something that the C preprocessor can
do into something similar that M4 can do.

> My other question is, why does M4 supported it as ifelse define combination 
> but not the more deeply nested ifelse ifelse define combination?

But M4 _does_ support nested conditionals.  The trick is to use correct
quoting to get correct execution; my guess without looking at your
script yet is that you failed to use proper quoting.

> My last question is, do you consider my supplied M4 source files as being 
> portable?

Portable to what? Any machine using only the POSIX subset of m4, or
portable to any machine with GNU m4?  As long as you use only the macros
required by POSIX, then your program is portable to any machine with
some implementation of m4; but along the same lines, GNU m4 is pretty
portable itself and can be compiled on a large variety of machines.  So
in general, it is fairly easy to get an input file to produce the same
output on a large number of machines.

> The information contained in the EMail and any attachments is confidential 
> and intended solely and for the attention and use of the named addressee(s).

Such disclaimers are unenforceable on publicly-archived lists. Some
people refuse to reply to such mail on principle, so you are better off
sending mail from a personal email account that does not slam your
employer's garbage legalese on the end when participating in open source
discussions.

Now, on to your script:

> Command-line:  cd /cygdrive/h/lang/m4
>                m4 -DARG1=OPT1[1-3] -DARG2=OPT2[1-2] -DARG3=OPT3 \
>                                                              
> TestIfelseDefine.m4

Insufficient shell quoting.  If you have a file named (literally)
'-DARG1=OPT12' in the /cygdrive/h/lang/m4 directory, the shell will glob
that and result in a different command line than you were intending.
Oh, I see - you are expecting me to execute:

m4 -DARG1=OPT11 -DARG2=OPT21 -DARG3=OPT3 script
m4 -DARG1=OPT12 -DARG2=OPT21 -DARG3=OPT3 script
...

up to 6 script executions to show different behaviors of your script.

> Purpose:       Document a possible bug in the current M4 Marco Language, that

s/Marco/Macro/

>                is, m4 (GNU M4) 1.4.17 Packaged by Cygwin (1.4.17-1), with a

Cygwin currently ships with m4 1.4.18, you may want to update your
installation; although it has no bearing on your particular script behavior.

> TestIfelseDefine.m4 arguments 1 and 2 are for options, argument 3 is for 
> results
> and is copied to new defined arguments 4, 5 and 6.
> Argument 3 shows that the work-around works.
>           IF arg1==opt11 THEN { IF arg2==opt21 THEN arg3=opt39 ELSE 
> arg3=opt38 }
> Argument 4 and 5 shows that single ifelse define works:
>                                   IF arg1==opt12 THEN arg4=opt49 ELSE 
> arg5=opt59
> Argument 6 shows that without the work-around it does not work.
>           IF arg1==opt11 THEN { IF arg2==opt21 THEN arg6=opt69 ELSE 
> arg6=opt68 }
> define(`ARG4',ARG3)dnl

This is (most-likely) underquoted - you are asking for the macro ARG4 to
be defined to whatever the ARG3 macro currently expands to.  Given your
command line above, it has the same effect as if you had done:

define(`ARG4', `OPT3')

Furthermore, note that the expansion of ARG4 will in turn be checked for
possible macros to expand, since there is no additional quoting being
supplied, if OPT3 is a macro name at the time ARG4 is expanded, then
your end result of expanding ARG4 will be the expansion of OPT3.

> define(`ARG5',ARG3)dnl
> define(`ARG6',ARG3)dnl

And likewise.

> define(`VERSION',`1.0.0')TestIfelseDefine.m4 VERSION: Before arg1=ARG1 
> arg2=ARG2 arg3=ARG3 arg4=ARG4 arg5=ARG5 arg6=ARG6 store_arg3=STORE_ARG3
> ifelse(ARG1,`OPT11',,`define(`STORE_ARG3',ARG3)')dnl

So this says: if the expansion of ARG1 matches the string OPT11, do
nothing, otherwise expand "define(`STORE_ARG3', ARG3)", which would
result in defining the macro STORE_ARG3 to contain the expansion of the
ARG3 macro (that is, the text OPT3 given your command line).  It may be
underquoted, but I don't yet know how you plan to use this.

> ifelse(ARG2,`OPT21',`define(`ARG3',OPT39)',`define(`ARG3',OPT38)')dnl

This says: if the expansion of ARG2 matches the string OPT21, then
expand "define(`ARG3', OPT39)" (there is no macro named OPT39, so
redefine the ARG3 macro to the text OPT39); if the text does not match,
then expand "define(`ARG3', OPT38)" (there is no macro named OPT38, so
redefine the ARG3 macro to the text OPT38).  Again, it is probably
underquoted if you ever plan on defining a macro named OPT38 or OPT39.

> ifelse(ARG1,`OPT11',,`define(`ARG3',STORE_ARG3)' `undefine(`STORE_ARG3')')dnl

This says: if the expansion of ARG1 matches the string OPT11, do
nothing, otherwise expand "define(`ARG3',STORE_ARG3)
undefine(`STORE_ARG3')", which in turn results in re-defining the ARG3
macro to the expansion of the STORE_ARG3 macro, then undefining the
STORE_ARG3 macro.

> ifelse(ARG1,`OPT12',`define(`ARG4',OPT49)',`define(`ARG5',OPT59)')dnl

Similar analysis.

> ifelse(ARG1,`OPT11',ifelse(ARG2,`OPT21',`define(`ARG6',OPT69)',`define(`ARG6',OPT68)'))dnl

Ah, here we get to your bug.  Remember, m4 ALWAYS looks for macro
expansions while collecting macro arguments.  But you did not quote the
inner ifelse macro.  Therefore, the inner ifelse is expanded first.

Execution-wise, you have told m4 to expand
"ifelse(ARG2,`OPT21',`define(`ARG6',OPT69)',`define(`ARG6',OPT68)')"
first, and use the result of that expansion as the 3rd argument to the
outer ifelse.

But what is the expansion of that inner ifelse?  If the expansion of
ARG2 matches the string OPT21, then it is the expansion of
"define(`ARG6',OPT69)"; if it does not match, then it is the expansion
of "define(`ARG6',OPT68)".  Either way, you are expanding the define
macro to define ARG6 (either to the value OPT69 or OPT68), and the
expansion of define is the empty string.

So you are in effect causing side-effects to happen (ARG6 was defined no
matter what) prior to executing the outer ifelse, which now looks like:
"ifelse(ARG1,`OPT11',)"
which is a no-op.

You probably WANTED to do this (where I'm intentionally using m4's
ability to ignore whitespace after comma to make things more legible):

ifelse(ARG1, `OPT11',
       `ifelse(ARG2, `OPT21',
               `define(`ARG6', OPT69)',
               `define(`ARG6', OPT68)')')dnl

Note the additional level of `' added around the inner ifelse.  Now, the
collection of arguments of the outer ifelse sees only a string, which
means that if the expansion of ARG1 matches the string OPT21, then
expand the inner string, so the inner ifelse happens only if the outer
ifelse was satisfied, rather than unconditionally.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org

Attachment: signature.asc
Description: OpenPGP digital signature


reply via email to

[Prev in Thread] Current Thread [Next in Thread]