Next: , Previous: Ifdef, Up: Conditionals


6.2 If-else construct, or multibranch

The other conditional, ifelse, is much more powerful. It can be used as a way to introduce a long comment, as an if-else construct, or as a multibranch, depending on the number of arguments supplied:

— Builtin: ifelse (comment)
— Builtin: ifelse (string-1, string-2, equal, [not-equal])
— Builtin: ifelse (string-1, string-2, equal-1, string-3, string-4, equal-2, ..., [not-equal])

Used with only one argument, the ifelse simply discards it and produces no output.

If called with three or four arguments, ifelse expands into equal, if string-1 and string-2 are equal (character for character), otherwise it expands to not-equal. A final fifth argument is ignored, after triggering a warning.

If called with six or more arguments, and string-1 and string-2 are equal, ifelse expands into equal-1, otherwise the first three arguments are discarded and the processing starts again.

The macro ifelse is recognized only with parameters.

Using only one argument is a common m4 idiom for introducing a block comment, as an alternative to repeatedly using dnl. This special usage is recognized by GNU m4, so that in this case, the warning about missing arguments is never triggered.

     ifelse(`some comments')
     =>
     ifelse(`foo', `bar')
     error-->m4:stdin:2: Warning: too few arguments to builtin `ifelse'
     =>

Using three or four arguments provides decision points.

     ifelse(`foo', `bar', `true')
     =>
     ifelse(`foo', `foo', `true')
     =>true
     define(`foo', `bar')
     =>
     ifelse(foo, `bar', `true', `false')
     =>true
     ifelse(foo, `foo', `true', `false')
     =>false

Notice how the first argument was used unquoted; it is common to compare the expansion of a macro with a string. With this macro, you can now reproduce the behavior of blind builtins, where the macro is recognized only with arguments.

     define(`foo', `ifelse(`$#', `0', ``$0'', `arguments:$#')')
     =>
     foo
     =>foo
     foo()
     =>arguments:1
     foo(`a', `b', `c')
     =>arguments:3

Since m4 is a macro language, it is even possible to write a macro that makes defining blind macros easier:

— Composite: define_blind (name, [value])

Defines name as a blind macro, such that name will expand to value only when given explicit arguments. value should not be the result of defn (see Defn). This macro is only recognized with parameters, and results in an empty string.

Defining a macro to define another macro can be a bit tricky. We want to use a literal `$#' in the argument to the nested define. However, if `$' and `#' are adjacent in the definition of define_blind, then it would be expanded as the number of arguments to define_blind rather than the intended number of arguments to name. The solution is to pass the difficult characters through extra arguments to a helper macro _define_blind.

As for the limitation against using defn, there are two reasons. If a macro was previously defined with define_blind, then it can safely be renamed to a new blind macro using plain define; using define_blind to rename it just adds another layer of ifelse, occupying memory and slowing down execution. And if a macro is a builtin, then it would result in an attempt to define a macro consisting of both text and a builtin token; this is not supported, and the builtin token is flattened to an empty string.

With that explanation, here's the definition, and some sample usage. Notice that define_blind is itself a blind macro.

     $ m4 -d
     define(`define_blind', `ifelse(`$#', `0', ``$0'',
     `_$0(`$1', `$2', `$'`#', `$'`0')')')
     =>
     define(`_define_blind', `define(`$1',
     `ifelse(`$3', `0', ``$4'', `$2')')')
     =>
     define_blind
     =>define_blind
     define_blind(`foo', `arguments were $*')
     =>
     foo
     =>foo
     foo(`bar')
     =>arguments were bar
     define(`blah', defn(`foo'))
     =>
     blah
     =>blah
     blah(`a', `b')
     =>arguments were a,b
     defn(`blah')
     =>ifelse(`$#', `0', ``$0'', `arguments were $*')

However, ifelse can take more than four arguments. If given more than four arguments, ifelse works like a case or switch statement in traditional programming languages. If string-1 and string-2 are equal, ifelse expands into equal-1, otherwise the procedure is repeated with the first three arguments discarded. This calls for an example:

     ifelse(`foo', `bar', `third', `gnu', `gnats')
     error-->m4:stdin:1: Warning: excess arguments to builtin `ifelse' ignored
     =>gnu
     ifelse(`foo', `bar', `third', `gnu', `gnats', `sixth')
     =>
     ifelse(`foo', `bar', `third', `gnu', `gnats', `sixth', `seventh')
     =>seventh
     ifelse(`foo', `bar', `3', `gnu', `gnats', `6', `7', `8')
     error-->m4:stdin:4: Warning: excess arguments to builtin `ifelse' ignored
     =>7

Naturally, the normal case will be slightly more advanced than these examples. A common use of ifelse is in macros implementing loops of various kinds.