Search notes:

m4

Dump all predefined macros

dumpdef(MACRO) prints the text that MACRO expands to to stdout.
Without argument, it prints all currently defined macros, including the predefined macros:
echo dumpdef | m4
I found the following predefined macros:
__file__ Quoted name of the current input file
__gnu__
__line__ Current line number
__program__ The name with which m4 was invoked
__unix__ Defined because running in a Unix (Linux) environment. See also __windows__.
builtin (name, [args…]) Calls name
changecom ([start], [end = ‘NL’]) Sets the text that delimits comments
changequote ([start = ‘’], [end = ‘'’])` Sets the delimiters with which text is quoted
debugfile ([file])
debugmode ([flags]) See also the -d option
decr (number) See also incr
define (name, [expansion]) Defines the expansion to which a macro (name) expands
defn (name…) Expands to the quoted definition of each name.
divert ([number = ‘0’]) Divert output. If number is negative, the diversion is discarded.
divnum
dnl Discard all characters up to and including the next newline.
dumpdef ([names…])
errprint (message, …)
esyscmd (shell-command) Read the output of a shell command. Compare with syscmd
eval (expression, [radix = ‘10’], [width]) Evaluate an integer expression
format (format-string, …)
ifdef Tests if a macro is defined
ifelse (string-1, string-2, equal-1, string-3, string-4, equal-2, …, [not-equal])
include (file) See also sinclude
incr (number) See also decr
index (string, substring) Search for substring in string
indir (name, [args…]) Call a macro indirectly
len (string) Length of string
m4exit ([code = ‘0’])
m4wrap (string, …) Stores string in a safe place, to be reread when end of input is reached. Expands to void.
maketemp (template)
mkstemp (template)
patsubst (string, regexp, [replacement]) Searches string for matches of regexp, and substitutes replacement for each match.
popdef (name…) popdef and pushdef are analogous to define and undefine
pushdef (name, [expansion])
regexp (string, regexp, [replacement])
shift (arg1, …)
sinclude (file)
substr (string, from, [length])
syscmd (shell-command) Compare with esyscmd
sysval
traceoff ([names…])
traceon ([names…])
translit (string, chars, [replacement])
undefine (name…)
undivert ([diversions…])
TODO: What about

Definitions

Definitions with arbitrary characters

It possible to define macros with arbitrary characters such as as ,,,, or X(1). Note that commas and parantheses have a special meaning when the text is parsed:
The definition of such macros can be queried with defn() although it cannot be simply expanded:
define(`X(1)', one)dnl
define(`,,,,', commas)dnl
ifdef(`X(1)',  X(1) is defined ,`X(1) is not defined')
ifdef(`X(2)',  X(2) is defined ,`X(2) is not defined')
ifdef(`,,,,', `,,,, is defined',`,,,, is not defined')
defn(`X(1)')
defn(`X(2)')
defn(`,,,,')
This script prodcues
X(1) is defined 
X(2) is not defined
,,,, is defined
one

commas

Diversions

By default, output is sent to stdout.
After calling divert(n), output is accumulated in memory or a temporary file. The diverted text is identified by the stream number n.
A diversion is ended by another call of divert.
undivert appends the diverted output to the current diversion.
Text recovered from a diversion is not scanned again for macros.

No diversion while arguments are collected

A limitation of m4 is that text which is part of an argument collection is not diverted:
dnl
dnl   https://lists.gnu.org/archive/html/m4-discuss/2024-06/msg00000.html
dnl
define(show_arguments,
`arg-1 = $1
arg-2 = $2')dnl
-------------------------
starting
divert(1)DIVERS START
divert(0)
started
-------------------------
show_arguments(
divert(1)DIVERSION ARG1`'divert(0) first argument,
divert(1)DIVERSION ARG2`'divert(0) second argument)
-------------------------
ending
divert(1)DIVERSION END
divert(0)
ended
-------------------------
When executed, this demonstration prints
-------------------------
starting

started
-------------------------
arg-1 = DIVERSION ARG1 first argument
arg-2 = DIVERSION ARG2 second argument
-------------------------
ending

ended
-------------------------
DIVERS START
DIVERSION END
A consequence of this limitation is that macros that change the current diversion cannot be used as an unquoted argument to another macro, but must be expanded at top level.
The m4sugar macro m4_expand diagnoses such attempts.

Some snippets

Simple example

#
#  m4 -P simple.m4
#
m4_define(NAME1, Harry)
m4_define(NAME2, Sally)
m4_define(MET, $1 met $2)
MET(NAME1, NAME2)
Github repository about-m4, path: /simple.m4
If run with -P, this snippet evaluates to
#
#  m4 -P simple.m4
#



Harry met Sally
The -P option prefixes predefined macros with m4_.

Dollar variables

define(`ARGS',
`First argument is:      $1'
`Number of arguments is: $#'
`All arguments:          $@')dnl
ARGS(one, two, three, four, five, six)
Github repository about-m4, path: /define-arguments.m4
This file expands to
First argument is:      one
Number of arguments is: 6
All arguments:          one,two,three,four,five,six

dumpdef

dumpdef prints the definition of a macro to stdout.
define(NUM,`42')dnl
define(TXT,``Hello world'')dnl
define(SUM3,`eval($1 + $2 + $3)')dnl
dumpdef(`NUM')dnl
dumpdef(`TXT')dnl
dumpdef(`SUM3')dnl
Github repository about-m4, path: /dumpdef.m4
Output:
NUM:	42
TXT:	`Hello world'
SUM3:	eval($1 + $2 + $3)
The output generated by dumpdef can be influenced with traceon and the -d command line option.

$* vs $@

$* and $@ expand to the list of arguments, but $@ also quotes the elements in the list.
divert(-1)

define(X1, aaa)
define(X2, bbb)
define(X3, ccc)

define(DOLLAR_AT, $@)
define(DOLLAR_STAR, $*)

divert(0)dnl
DOLLAR_AT(`X1', `X2', `X3')
DOLLAR_STAR(`X1', `X2', `X3')
Github repository about-m4, path: /dollar-at-vs-star.m4
The output is
X1,X2,X3
aaa,bbb,ccc

$n in macros

Replacement of arguments happens before rescanning, regardless of how many nesting levels of quoting appear in the expansion, i. e. $1 etc. cannot be «easily» quoted in a macro.
define(`MACRO',
`$1 = $1
`$2' = $2
``$3'' = $3
```$4''' = $4
$'`5 = $5
`$'`5' = $6')dnl
MACRO(A,B,C,D,E,F)
Github repository about-m4, path: /dollar-n-in-parameters.m4
A = A
B = B
`C' = C
``D'' = D
E = E
$5 = F

Macro expansion in strings

A macro is expanded in "strings" (because " has no special meaning in m4).
define(text, Hello world)dnl
printf("text\n");
Github repository about-m4, path: /within-quotes.m4
This snippet expands to
printf("Hello world\n");

Defining a macro with arguments

define(`swapwords', `$2 $1')dnl
swapwords(three, swapwords(two, one))
Github repository about-m4, path: /define-with-arguments.m4
This file expands to
one two three

pushdef / popdef

Note, the order of popdef does not have to be the reverse of pushdef.
define(`VAL_1', one)dnl
define(`VAL_2', two)dnl
define(`VAL_3', three)dnl
VAL_1 VAL_2 VAL_3
pushdef(`VAL_2', TWO)dnl
pushdef(`VAL_1', ONE)dnl
VAL_1 VAL_2 VAL_3
popdef(`VAL_2')dnl
VAL_1 VAL_2 VAL_3
popdef(`VAL_1')dnl
VAL_1 VAL_2 VAL_3
Github repository about-m4, path: /pushdef.m4
The output is:
one two three
ONE TWO three
ONE two three
one two three

shift

define(S, `first element      : $1
remaining arguments: shift($@)')dnl
S(one, two, three, four, five)
Github repository about-m4, path: /shift.m4
Output:
first element      : one
remaining arguments: two,three,four,five

ifelse

ifelse((string-1, string-2, equal-1, string-3, string-4, equal-2, …, [not-equal]) is approximately the same as the following in another language:
if ( string-1 == string-2 ) {
   equal-1
}
else if ( string-3 == string-4 ) {
   equal-2
}
…
else {
  not-equal
}
The else… part is optional

Example 1

Note the quotes around ifelse(…). Without them, the output would be different.
define(testEquality, `ifelse($1,$2,$1 == $2,$1 != $2)')dnl
testEquality(foo,bar)
testEquality(baz,baz)
Github repository about-m4, path: /ifelse.m4
foo != bar
baz == baz

Example 2

define(NUM,11)dnl
define(TXT,Hello world)dnl
ifelse(
   NUM,42, ``NUM' is 42',
   TXT,Hello world, ``TXT' is Hello world',
 ``NUM' is not 42 and `TXT' is not Hello world'
)
Github repository about-m4, path: /ifelse-2.m4
The output of this example is:
TXT is Hello world

incr

The following snippet defines H1, H2 and H3 which produces enumerated HTML chapters.
When H1 is invoked, it increments the value of cur_h1_nr, sets cur_h2_nr to 0 and evaluates to <h1>cur_h1_nr. …</h1>.
Similarly,wWhen H2 is invoked, it increments the value of cur_h2_nr, sets cur_h3_nr to 0 and evaluates to <h2>cur_h1_nr.cur_h2_nr. …</h2>.
Finally, invoking H3 increases cur_h3_nr and then evaluates to <h3>cur_h1_nr.cur_h2_nr.cur_h3_nr. …</h3>.
divert(-1)
define(`cur_h1_nr', 0)

define(`H1',
`define(`cur_h1_nr',incr(cur_h1_nr))'dnl
`define(`cur_h2_nr', 0)'dnl
`<h1>cur_h1_nr. $1</h1>'dnl
)dnl

define(`H2',
`define(`cur_h2_nr',incr(cur_h2_nr))'dnl
`define(`cur_h3_nr', 0)'dnl
`<h2>cur_h1_nr.cur_h2_nr. $1</h2>'dnl
)dnl

define(`H3',
 `define(`cur_h3_nr',incr(cur_h3_nr))'dnl
`<h3>cur_h1_nr.cur_h2_nr.cur_h3_nr. $1</h3>'dnl
)dnl

divert(1)dnl
H1(Introduction)
 H2(Purpose)
  H3(Background)
  H3(Objectives)
  H3(Considerations)

 H2(Scope)
  H3(Coverage)
  H3(Limitations)

H1(Main Body)
 H2(Analysis)
  H3(Key Findings)
  H3(Supporting Data)
  H3(Subsection Three)
  H3(Subsection Four)

 H2(Discussion)
  H3(Interpretation)
  H3(Challenges)

H1(Conclusion)
 H2(Summary)
  H3(Key Points)
  H3(Implications)

 H2(Future Work)
  H3(Recommendations)
  H3(Next Steps)
Github repository about-m4, path: /html-chapter-enumeration.m4
<h1>1. Introduction</h1>
 <h2>1.1. Purpose</h2>
  <h3>1.1.1. Background</h3>
  <h3>1.1.2. Objectives</h3>
  <h3>1.1.3. Considerations</h3>

 <h2>1.2. Scope</h2>
  <h3>1.2.1. Coverage</h3>
  <h3>1.2.2. Limitations</h3>

<h1>2. Main Body</h1>
 <h2>2.1. Analysis</h2>
  <h3>2.1.1. Key Findings</h3>
  <h3>2.1.2. Supporting Data</h3>
  <h3>2.1.3. Subsection Three</h3>
  <h3>2.1.4. Subsection Four</h3>

 <h2>2.2. Discussion</h2>
  <h3>2.2.1. Interpretation</h3>
  <h3>2.2.2. Challenges</h3>

<h1>3. Conclusion</h1>
 <h2>3.1. Summary</h2>
  <h3>3.1.1. Key Points</h3>
  <h3>3.1.2. Implications</h3>

 <h2>3.2. Future Work</h2>
  <h3>3.2.1. Recommendations</h3>
  <h3>3.2.2. Next Steps</h3>

ifdef

ifdef checks if a token is a defined macro.
define(`check_if_defined',
  `ifdef(`$1',
  `$1 is defined',
  `$1 is not defined')')dnl
check_if_defined(`unobtainium')
check_if_defined(`ifdef')
Github repository about-m4, path: /ifdef.m4
unobtainium is not defined
ifdef is defined

Create a sequence of numbers

define(`sequence', `$1 ifelse($1, $2, , `sequence(incr($1), $2)')')dnl
sequence(13,21)
Github repository about-m4, path: /sequence.m4
13 14 15 16 17 18 19 20 21

eval

eval operates on 32-bit integers.
eval(4+5) 9
eval(255, 16) ff
eval(0xff) 255
eval(42 == 42) 1
eval(42 == 11) 0
eval(5>2) 1
eval(eval(2==2)+eval(4==1)) 1
eval(2**30) 1073741824
eval(2**31) -2147483648
eval(2**31-1) 2147483647
eval(2**32) 0
eval(7.3 + 9.2) bad expression in eval (bad input)

translit

The macro to_uppercase evaluates a string in uppercase letters:
define(`to_uppercase', `translit($1, `[a-z]', `[A-Z]')')dnl
to_uppercase(Hello world)
Github repository about-m4, path: /to-uppercase.m4

Whitespaces in parameters

Unquoted leading whitespace is stripped off all arguments.
define(`MACRO',
`$`'1' = >$1<'
`$`'2' = >$2<'
`$`'3' = >$3<'
`$`'4' = >$4<'
)dnl
MACRO(A, B, C ,
D
)
Github repository about-m4, path: /whitespace-in-parameters.m4
$1 = >A<'
$2 = >B<'
$3 = >C <'
$4 = >D
<'

regexp

Extract the first continous block of digits

regexp(abc 42 def 99 ghi, \([0-9]+\), \1)
Github repository about-m4, path: /regexp-digit.m4

Recursion

Calculate the sum of a list

The sum of a list can be calculated recursively:
define(sum,
  `ifelse($#,1,
    $1,
   `eval($1+sum(shift($@)))')')dnl
dnl
sum(15)
sum(15,5)
sum(15,5,7)
sum(15,5,7,3)
Github repository about-m4, path: /recursion-sum.m4
15
20
27
30

Fibonacci

Calculating the nth fibonacci number:
define(fibonacci,`eval(
  ifelse(eval($1 <= 2), 1,1,
`   eval(fibonacci(decr(decr($1))) +
         fibonacci(     decr($1)))'
    )
  )')dnl
fibonacci(1)
fibonacci(2)
fibonacci(3)
fibonacci(4)
fibonacci(5)
fibonacci(6)
fibonacci(7)
fibonacci(8)
Github repository about-m4, path: /fibonacci.m4
Note, the outermost eval() in the macro is included to remove whitespace from the result.

while

The following macro while must be called with two arguments.
As long as the first evaluation of eval($1) is 1 (i. e. true), the second argument is evaluated followed by yet another evaluation of while(…)
define(while,
  `ifelse(eval($1), 1, `$2'`while(`$1', `$2')',)')

dnl
dnl   Use incr()
dnl
define(`i', 4)
while(`i < 10',
``i' = i,
define(`i', incr(i))')

dnl
dnl   Use eval(
dnl
define(`i', 4)
while(`i < 10',
``i' = i
define(`i', eval(i+2))')
Github repository about-m4, path: /while.m4
i = 4,
i = 5,
i = 6,
i = 7,
i = 8,
i = 9,



i = 4
i = 6
i = 8

Nested quotes

Expansion of a macro «de-quotes» quoted elements by one level.
define(`NESTED_0', `nested zero')dnl
define(`NESTED_1', `nested one' )dnl
define(`NESTED_2', `nested two' )dnl
define(`DEFINITION', `NESTED_0 `NESTED_1' ``NESTED_2'' `NESTED_1'')dnl
define(`EXPANSION_0', `DEFINITION')dnl
define(`EXPANSION_1',  EXPANSION_0 )dnl
define(`EXPANSION_2',  EXPANSION_1 )dnl
`DEFINITION  =' DEFINITION
`EXPANSION_0 =' EXPANSION_0
`EXPANSION_1 =' EXPANSION_1
`EXPANSION_2 =' EXPANSION_2
Github repository about-m4, path: /nested-quotes.m4
DEFINITION  = nested zero NESTED_1 `NESTED_2' NESTED_1
EXPANSION_0 = nested zero NESTED_1 `NESTED_2' NESTED_1
EXPANSION_1 = nested zero nested one  NESTED_2 nested one  
EXPANSION_2 = nested zero nested one  nested two  nested one   

Diversion

divert(2)dnl
two
divert(4)dnl
four
divert(1)dnl
one
divert(3)dnl
three
Github repository about-m4, path: /divert.m4
This evaluates to
one
two
three
four

Regular expressions

The regular expression syntax and capabalities are those of Emacs which is similar to Basic Regular Expressions (BRE).
Support for Extended Regular Expressions (ERE) is scheduled for GNU M4 2.0. Unfortunately, I found no hint when this release will be. The commit log of the 2.0 branch has stalled 2017 (checked June 2024).

Version 1.4 vs 1.6

m4sugar/foreach.m4 comments that M4 1.4 rescans every byte of $@ which results in a quadratic nature when using $@ for recursion
In M$ 1.6, $@ is only scanned once and then back-references are made to information stored about the scan.

See also

m4sugar introduces additional generic macros.
Autoconf

Links

Exploiting the m4 Macro Language by Kenneth J. Turner was very helpful when I investigated m4.

Index

Fatal error: Uncaught PDOException: SQLSTATE[HY000]: General error: 8 attempt to write a readonly database in /home/httpd/vhosts/renenyffenegger.ch/php/web-request-database.php:78 Stack trace: #0 /home/httpd/vhosts/renenyffenegger.ch/php/web-request-database.php(78): PDOStatement->execute(Array) #1 /home/httpd/vhosts/renenyffenegger.ch/php/web-request-database.php(30): insert_webrequest_('/notes/developm...', 1759406386, '216.73.216.42', 'Mozilla/5.0 App...', NULL) #2 /home/httpd/vhosts/renenyffenegger.ch/httpsdocs/notes/development/tools/m4/index(802): insert_webrequest() #3 {main} thrown in /home/httpd/vhosts/renenyffenegger.ch/php/web-request-database.php on line 78