Academic Integrity: tutoring, explanations, and feedback — we don’t complete graded work or submit on a student’s behalf.

my_title

ID: 3637954 • Letter: M

Question

Define an interesting macro in m4
---------------------------------

Define and test a non-trivial macro, using the m4 macro processor.
"Non-trivial" should be read as "not as simple as 'factorial'".

Here are some possibilities to choose from. Any additions to the list
would be most welcome.

1. hanoi( N, `A', `B', `C' ) (where N is some integer) would be
expanded to a recipe for solving the problem of Towers of Hanoi
for N disks which should be moved from peg A to peg C, using B as
an auxiliary peg.

2. fib( N ) (where N is some integer) would be expanded to the N'th
Fibonacci number.

3. ... (Your own ideas.)


Your solution should be a file that contains the macro
definition and some example calls. It should be ready to run
on m4. In the head comment (use "dnl" for that) specify the version of m4 that you were using.



------------------------------------------------------------------------

Explanation / Answer

m4 is a great tool to boost your productivity. Have a lot of fun writing m4 scripts. This page will learn you the most essential parts, enough to start writing great scripts. Introduction to the m4 macro processor The m4 macro processor has been in use on Unix systems for a long time. The main purpose of m4 is to generate files. Initially m4 was created as a pre-processor for Fortran program code. This was about 30 years ago. Today m4 is used as a tool to generate configuration files, and is most famous as a generator of the sendmail.cf file. m4 is still very useful today. I use m4 mostly to generate xhtml files. The combination of awk, m4 and make provide a powerful tool and together they are a good replacement for a content management system. The combination of awk, m4 and make builds xhtml-files with good working links in menus. So it works like some kind of templating system. Also m4 helps me to separate content from (xhtml-)code I keep all the xhtml-code in one file, the content for each page in a separate content-file per page and have put the m4 scripts in a few files on their own. There is one single configuration file which contains a list of files to generate, with the page-titles, menu definitions etc. Output is generated by running make. The result of this: Change of xhtml-code requires only the editing of one file. Running make will build the new version of all the web pages through a single command. Adding a web page to the site is trivial. A line is added to the configuration file, telling the name of the new file, its title and if and how it should be adopted in the different menus of the site. The content of the new page is put in its own file in the content sub-directory. Running make will build the new web page and build new versions of the pages that have altered menus. Updating the content of a web page only requires the editing of the specific content file in the sub-directory and running make to build the new version of the web page. I have build a number of websites this way and have maintained them this way for some years now. And I am still happy with this solution :) Because all the scripts as well as the content are ordinary text files, it is very easy to keep them in a CVS repository. This allows not only for reversibility of changes but also provides a very good mechanism to keep everything neatly organized. As a bonus the CVS repository simplifies the backup procedures. Below follows a small introduction in the basic usage of m4. First steps in m4 The standard syntax of a macro definition in m4 is: define(`keyword',`replacement') Notice the back tick (`) and the single quote ('), these are the standard delimiters. Here we use it in an example: define(`yoo',`Hello World!') I say this: yoo Put this line into a file, called my_first_m4_program and run it with m4: m4 my_first_m4_program I say this: Hello World! Redirect the output of m4 to a file Because m4 transfers its output to stdout, it is very simple to redirect the output of m4 to a file: m4 my_first_m4_program > test_file cat test_file I say this: Hello World! We simply put " > filename" behind the m4 command. m4 macro definition A simple macro just replaces some part of the text on the input. Although this is a simple mechanism it lead to powerful m4 scripts. Literal text is placed between text-delimiters (standard: ` and '), variables not. The statement can be broken into several lines: define(`yoo', `Hello World!' ) I say this: yoo This will result in some extra white lines in the output, though. Also it is possible to let the second part of the statement be text that is several lines: define(`htmlheader', ` my_title ') Parameter list to mimic function-like calls A M4-definition can be enhanced with a parameter list: define(`my_value',`$1_file') my_value(`test') This last command (my_value(`test') returns the output test_file. The first parameter is addressed with $1, the second with $2, etc. Conditional statements in m4 Conditional statements enhances the usefulness of our scripts. This is the syntax: ifelse(`first_text',`second_text',`true_action',`false_action') ifelse: the m4 command first_text: this is the first parameter second_text: this is the second parameter true_action: this is the output if first parameter and second parameter are equal false_action: this is the output if first parameter and second parameter are not equal An example in real life usage: ifelse(my_filename,`index.html',`Home',`Home') This is a part of a m4 macro that creates the menu in a web page. If the current page has the filename "index.html" (which is fed to the macro in the variable my_filename) then the output is a line with just the word "Home", otherwise the output is a hyperlink to the homepage. Nesting macros m4 macros can be nested. This means that one macro uses the output of another macro to modify that. When combined with conditional statements this results in a very strong mechanism. define(`my_sidebar',`include(content/$1_sidebar)') Another, more complex example: define(`my_menu',`
  • ifelse(filename,$1,`$1_menu',`$1_menu')
  • ') Feeding the value of a variable With the switch -D a variable can be given a value at the invocation of the m4 program on the command-line: m4 -D variable=value my_program.m4 Including files By including a file it is expanded on the location of the include statement. include(`filename') I use this mechanism to include the content into templates. This way all the content is kept separately in a sub-directory. Also parts of m4-code can be put in different files, as the m4 processor processes them just as if they were put in place of the include command. An example of how this could be done: include(`pagedefinitions') include(`webmenudefinitions') include(`xhtmldefinitions') build_htmlheader(`current_webpage') insert_content(`current_webpage') build_htmlfooter(`current_webpage') pagedefinitions: definitions of the current page (title, content of meta tags like description, etc.) webmenudefinitions: set of m4 macros to build the menus of your web pages xhtmldefinitions: set of m4 macros to build the xhtml code (you could think of this like some kind of template mechanism) current_webpage: variable which holds the name of the current file to be generated build_htmlheader: macro that assembles the xhtml to the tag insert_content: set of macros that generate the content and add this to the partly build xhtml file build_htmlfooter: completes the xhtml file with sidebar, bottom menu, etc. The use of divert in m4 macros It is very easy to introduce a lot of whitespace in the output of your m4 macros. The first step to reduce the generated whitespace is the use of the command dnl (dnl: delete everything from here to the first newline). This approach will still leave a lot of whitespace as a result of macro-definitions. The next step is the use of the command divert (divert: re-route the output to a different stream). When we issue the command divert(-1) the output is sent to stream -1, which is a non-existing stream (like /dev/null). After some commands like macro definitions we then issue the command divert to reset to output stream to its original. Example of the use of divert to reduce whitespace divert(-1) define(`my_macro1', `some_macro_expansion' ) define(`my_macro2', `another_macro_expansion' ) divert This makes it possible to write m4 macros that are easy to read and maintain. I build most of my websites using this. I build a small system that uses awk to generate Makefiles, and m4 to build the pages.