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
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)
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)
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
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')
The output is
X1,X2,X3
aaa,bbb,ccc
$n in macros
define(`MACRO',
`$1 = $1
`$2' = $2
``$3'' = $3
```$4''' = $4
$'`5 = $5
`$'`5' = $6')dnl
MACRO(A,B,C,D,E,F)
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");
This snippet expands to
printf("Hello world\n");
Defining a macro with arguments
define(`swapwords', `$2 $1')dnl
swapwords(three, swapwords(two, one))
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
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)
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)
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'
)
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)
<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')
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)
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)
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
)
$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)
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)
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)
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))')
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
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
This evaluates to
one
two
three
four