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
signature.asc
Description: OpenPGP digital signature