Hints and Tips for the M4 macro processor ------------------------------------------------------------------------------- What is M4 M4 is a very old text file macro processor, sort of like the C-PreProcessor used in C and other Program Compilers. However unlike those it has a lot finer control on the resulting text output, and can have features enables and or disabled as needed. NOTE: in the following => denotes output from m4 (as per GNU m4 info guide) Gnu m4 has no manpage! and Solaris manpage is the non-gnu basic m4 information. The best manual I have seen (from GNU info).. http://www.seindal.dk/rene/gnu/ I have yet to find a manual with everything in a single printable document. But at least the above has a decent amount of info per page! ------------------------------------------------------------------------------- M4 macro comments start lines with dnl, and that line is not included in the output dnl this is a comment and is completely ignored ------------------------------------------------------------------------------- Stopping extra blank lines and spaces To stop M4 leaving blank links add the "dnl" (delete new line) to remove the following newline character (and all up to that character. NOTE: leading unquoted whitespace is never part of an argument, but trailing whitespace always is included!!! define(TEST, "$1" "$2" "$3")dnl TEST( a, b ,c ) =>"a" "b " "c " This is also the case for the define itself define(TEST, $1 )dnl "TEST(a)" =>"a " ------------------------------------------------------------------------------- M4 Quotes and shell commands To prevent M4 processing a macro or string containing macros you need to surround the string with quote characters (by default ` and ' ) as two different characters are used you can easilly embed quotes within quotes for multiple macro expandsion of the results. However as the default quotes are also heavily used by the various UNIX shells it isn't very usefull. As such I usally change the quotes to something rarely used by shell commands (like { and } ). changequote`'changequote(`{',`}')dnl Set quotes to { and } Note the way I change the quotes back to defaults BEFORE reassigning them. Also note that changequote(,) will set the quote strings to a NULL string! Using with shell command changequote`'changequote(`{',`}')dnl Set quotes to { and } define(SHELLTEST, {test `wc < "$1"` -eq 1 } )dnl SHELLTEST(file.log) =>test `wc < "file.log"` -eq 1 ------------------------------------------------------------------------------- Undefining a Macro When undefining a macro you MUST quote the macro being undefined to prevent expandsion. define(HOME, /home/someone)dnl undefine(`HOME')dnl ------------------------------------------------------------------------------- ifdef() and quotes The arguments to ifdef must be quoted. The reason is that ifdef will automatically remove one layer of quoting over its arguments. This is not important for most ifdef()s. But if the argument contains a define() or other macro you don't what expanded before the ifdef() is finished then you MUST QUOTE ALL ARGUMENTS!!! For example ifdef(`DEFAULT', define(`DOIT')) will always define `DOIT' weather `DEFAULT' is defined or not! This is because m4 will expand any macros immediately when that are found. That is while m4 is still reading the arguments for ifdef(), it will read and expand the define() macro BEFORE the ifdef() is finished. To do this correctly you MUST add a extra layer of quoting which the ifdef will remove. EG: ifdef(`DEFAULT', `define(`DOIT')') ------------------------------------------------------------------------------- Macro arguments and quotes The macro arguments should also be quoted within the macro. For example define(`a',`b') define(`test',`"$1"') test(`a') =>"b" That is the argument was also macro expanded! This is because the result of macro expandsion of $1 is then also expanded! The proper way... define(`a',`b') define(`test',`"`$1'"') test(`a') =>"a" HINT: add some m4 quotes to the quoted raw string. If they disappear you have one more level of expandsion than expected. EG: continuing the above.. test(`quote `' test') =>"quote `' test" Perfect! ------------------------------------------------------------------------------- Array Storage trick (from GNU m4 info page) The first argument to define does not have to be a simple word. It can be any text string. A macro with a non standard name cannot be invoked in the normal way, as the name is not recognised. It can only be referenced by the builtins section Indirect call of macros and section Renaming macros. Arrays and associative arrays can be simulated by using this trick. define(`array', `defn(format(``array[%d]'', `$1'))') define(`array_set', `define(format(``array[%d]'', `$1'), `$2')') array_set(4, `array element no. 4') array_set(17, `array element no. 17') Then test the above with... array(4) =>array element no. 4 array(eval(10+7)) =>array element no. 17 Change the %d to %s and it is an associative array. NOTES: defn() is used to return the string stored in the given macro quoted so that no further macro expansion is performed. EG: the word "array" in the stored string is not expanded. format() is GNU m4 specific. ------------------------------------------------------------------------------- Forloop macro foreach( var, start, stop, output) This uses a setup call and recursion to setup a incremented loop. Actually this only does a comparision and could probably be improved. define(`forloop', `pushdef(`$1', `$2')_forloop(`$1', `$2', `$3', `$4')popdef(`$1')')dnl define(`_forloop', `$4`'ifelse($1, `$3', , `define(`$1', incr($1))_forloop(`$1', `$2', `$3', `$4')')')dnl Example... forloop(`i', 1, 8, `i ') =>1 2 3 4 5 6 7 8 Nested forloops... define(`nl', ` ') forloop(`i', 1, 4, `forloop(`j', 5, 8, `(i, j) ')nl')dnl =>(1, 5) (1, 6) (1, 7) (1, 8) =>(2, 5) (2, 6) (2, 7) (2, 8) =>(3, 5) (3, 6) (3, 7) (3, 8) =>(4, 5) (4, 6) (4, 7) (4, 8) ------------------------------------------------------------------------------- Note $0 is the name of the current macro in which it is expanded! Very useful for generating variable names define(`Var', `define(`$0Value', `$1')')dnl Var(`set to this')dnl VarValue ------------------------------------------------------------------------------- dnl dnl Array storage -- combine into one function dnl dnl array_set(`name', `element', `value') dnl array(`name', `element') dnl define(`array', `defn(format(``$1[%d]'', `$2'))') define(`array_set', `define(format(``array[%d]'', `$1'), `$2')') dnl Then test the above with... array_set(4, `array element no. 4') array_set(17, `array element no. 17') array(4) =>array element no. 4 array(eval(10+7)) =>array element no. 17 ------------------------------------------------------------------------------- dnl dnl Examples from "Exploiting the m4 Macro Language" dnl divert(-1) dnl ---- dnl Curried Macros -- named after the mathematician Curry. dnl These are macros used to set initial or common arguments to other macros. dnl They are very common in recursion to initialise the recursive macro, dnl adding other arguments for the recursion variables. dnl For example "Double" is a curried macro for Multiply define(`Multiply', `eval($1*$2)') define(`Double', `Multiply($1, 2)') dnl Testing --> 5 doubled gives 10 dnl divert(0)dnl dnl 5 doubled gives Double(5) dnl undivert(0)m4exit(0) dnl ---- dnl Recursion dnl To prevent a recursive function from expanding into a huge string before dnl being evaluated, you must evaluate and carry results in the recursive dnl macro call so only the final expandsion gives the final result. dnl Fact(n) dnl WARNING: n > 14 will overflow without any error by m4 define( `Fact', `FactAux($1,1)') dnl FactAux(n, result_so_far) dnl Result is the, collected results, or continued calcualion define( `FactAux', `ifelse( eval($1<=0),1,$2, `FactAux(decr($1), eval($1*$2))')') dnl Testing --> 1278945280 dnl divert(0)dnl dnl Fact(14) dnl undivert(0)m4exit(0) dnl ---- dnl Mutual Recursion dnl Flip starts with a 'On' Flop starts with 'Off' define( `Flip', `ifelse( eval($1>0),1,`On Flop(decr($1))')') define( `Flop', `ifelse( eval($1>0),1,`Off Flip(decr($1))')') dnl Testing ---> On Off On Off On dnl divert(0)dnl dnl Flip(5) dnl undivert(0)m4exit(0) dnl ---- dnl Iteration dnl while( `while_condition', `body' ) define( `while', `ifelse( eval($1), 1, `$2`'while(`$1',`$2')')') dnl repeat( `body', `until_condition' ) define( `repeat', `$1`'ifelse( eval($2), 0, `repeat(`$1',`$2')')') dnl Testing --> i=1 i=2 i=3 i=4 -- i=5 i=4 i=3 i=2 i=1 dnl divert(0)dnl dnl define(`i', 1)dnl dnl while( `i<5', ``i='i define(`i', incr(i))') -- dnl dnl repeat( ``i='i define(`i', decr(i))', `i==0') dnl undivert(0)m4exit(0) dnl ---- ------------------------------------------------------------------------------- dnl dnl Examples from "Exploiting the m4 Macro Language" dnl divert(-1) dnl This sets up a specific method of generating sub-lists dnl NOTES.... dnl * Lists are strings of the form `[...;...;...]' dnl * Empty list is of the form `[]' dnl * An empty List is also a list of a null string!!! dnl * However a list contain sub-lists `[...;[..;..];...]' dnl In which case the sublist is a thought of as a single element dnl dnl --- dnl Push(`list',`element') Push an element onto end of a list define(`subtest', `substr(`$1',0,decr(len(`$1')))') define(`abc', 1) define(`de', 2) divert(0)dnl subtest(`-abc-de--') undivert(0)m4exit(0) define( `Push', `ifelse(`$1',`[]', `[`$2']', `substr(`$1',0,decr(len(`$1')));`$2']')') define(`abc', 1) define(`de', 2) define(`fghi', 3) divert(0)dnl Push(`[]', `abc') --> `[abc]' Push(`[abc;de]', `fghi') --> `[abc;de;fghi]' dnl --- dnl Head(`list') Extract the first element from a List -------------------------------------------------------------------------------