M4 macros that provide some of the CPP conditional section processing

I heard a long time ago that CPP was derived from M4, and read that a few folks including myself have looked for IF / ENDIF type macros to use on other projects. e.g. CPP is not ideal in that it wants to take extra steps with the expectation of C syntax, like tokenizing the source.

So not finding anything like this, I wrote some that seem to work pretty well. These versions don't have the '#' prepended but I suppose you could do that using the changeword macro.

Here they are. the test file is pretty random but it does show they mostly behave as expected. smile

cpp_macros.m4

m4_divert(`-1') m4_changecom(`//')
//
//      Handy M4 macro definitions
//

// Housekeeping macros
m4_define(`_', m4_defn(`m4_dnl'))_  Unobtrusive dnl, used for readability
m4_define(`nop', `m4_ifelse(`$#', 0, ``$0'',)')_ Allows for better formatting by tossing white space between ()s

// Now that # is no longer the comment character, we could allow it as the first character of a macro name
ifdef(`changeword', `', `errprint(` skipping: no changeword support
')m4exit(`77')')_
// m4_changeword(`[_a-zA-Z#][_a-zA-Z0-9]*')
 
//
//      Some handy C preprocessor macros to implement conditional source code sections
//      Note that we most likely use these in a non C environment, so its desierable to match its expression
//      syntax for clarity. Here is an example of how to do that.
//

// M4 expressions try be as similar to C as possible, that goes for CPP as well. Though youre probably
// using M4 on another language to get some of the CPP features in that environment. Then you may also want to
// emulate that languages expression syntax as much as possible. If so then here is an example of how to do that.
// These substitutions convert expressions that use words for logical operators, etc... to M4 syntax as much as possible:

_ m4_define(`X', `bar = (Not True and falSE or True * true) + 3 ^ 2 != 7 = foo')_ Example expression to be converted
_ m4_define(`X', m4_translit(X, `a-z', `A-Z'))_
_ m4_define(`X', m4_patsubst(X, `TRUE', `1'))_
_ m4_define(`X', m4_patsubst(X, `FALSE', `0'))_
_ m4_define(`X', m4_patsubst(X, `NOT', `!'))_
_ m4_define(`X', m4_patsubst(X, `AND', `&&'))_
_ m4_define(`X', m4_patsubst(X, `OR', `||'))_
_ m4_define(`X', m4_patsubst(X, `\^', `**'))_
_ m4_define(`X', m4_patsubst(X, `\([^!=]\)=\([^=]\)', `\1==\2'))_
_ m4_define(`X', m4_patsubst(X, `\([^!=]\)=\([^=]\)', `\1==\2'))_ Just to show its bug free ;-)
_ m4_define(`X', m4_patsubst(X, `<>', `!='))_
_ X

// A helper function to convert another style of expressions to C style
// This is equivilent to the above substitutions
m4_define(`_EXPR2C', `m4_patsubst(m4_patsubst(m4_patsubst(m4_patsubst(
    m4_patsubst(m4_patsubst(m4_patsubst(m4_patsubst(m4_translit(
    `$1', `a-z', `A-Z'), `TRUE', `1'), `FALSE', `0'),
    `NOT', `!'), `AND', `&&'), `OR', `||'), `\^', `**'),
    `\([^!=]\)=\([^=]\)', `\1==\2'), `<>', `!=')')

// Here is a pass through function to just use the M4 native syntax
// m4_define(`_EXPR2C', `$1')

// A helper function for expr evals
// Note that eval returns the null string for incorect syntax, (along with an error message to stderr), and
// it's helpfull to also treat that as 0.
m4_define(`_EXPREVAL',
    `m4_define(`__EXPREVAL__', m4_eval(_EXPR2C(`$1')))nop(
    )m4_ifelse(__EXPREVAL__, `', 0, __EXPREVAL__)')

//  We need to define this to show that top level text is processed
m4_define(`__CPP_STACK__', 0)

// The CPP defined macro
m4_define(`DEFINED', `m4_ifelse(`$#', 0,
    ``$0'',
    `m4_ifdef(`$1', 1, 0)')')

// The CPP IFDEF macros
m4_define(`IFDEF',  `m4_ifdef(`$1', `_IFTRUE(1)', `_IFTRUE(0)')_')
m4_define(`IFNDEF', `m4_ifdef(`$1', `_IFTRUE(0)', `_IFTRUE(1)')_')

// Helper function that includes the section if the argument is not `0'
m4_define(`_IFTRUE', `m4_ifelse(m4_eval((__CPP_STACK__ < 0) || !`$1'), 1,
    `m4_divert(-1)')m4_pushdef(`__CPP_STACK__', m4_divnum)')

// The CPP #IF macro
m4_define(`IF', `m4_ifelse(m4_eval((__CPP_STACK__ < 0) || (_EXPREVAL(`$1') == 0)), 1,
    `m4_divert(-1)')m4_pushdef(`__CPP_STACK__', m4_divnum)_')

// The CPP #ELIF macro
m4_define(`ELIF',
    `m4_define(`__ELSEVAL__', __CPP_STACK__)nop(
    )m4_popdef(`__CPP_STACK__')nop(
    )m4_ifelse(m4_eval((__CPP_STACK__ >= 0) && (__ELSEVAL__ < 0)), 1,
        `m4_ifelse(m4_eval(_EXPREVAL(`$1') != 0), 1, // This If / Else is cotained in an enabled block
            `m4_divert(__CPP_STACK__)', `m4_divert(-1)')m4_pushdef(`__CPP_STACK__', m4_divnum)',
        `m4_ifelse(`Comment: In off block, put stack back')m4_divert(-1)m4_pushdef(`__CPP_STACK__', __ELSEVAL__)')_')

// The CPP #ELSE macro
m4_define(`ELSE',
    `m4_define(`__ELSEVAL__', __CPP_STACK__)nop(
    )m4_popdef(`__CPP_STACK__')nop(
    )m4_ifelse(m4_eval((__CPP_STACK__ >= 0) && (__ELSEVAL__ < 0)), 1, // This If / Else is cotained in an enabled block
        `m4_divert(__CPP_STACK__)', `m4_divert(-1)')m4_pushdef(`__CPP_STACK__', m4_divnum)_')

// The CPP #ENDIF macro FIXME: check if we poped too many times
m4_define(`ENDIF', `m4_popdef(`__CPP_STACK__')m4_divert(__CPP_STACK__)_')

// Selective Invoke
// A way to prevent macro expansion in non selected code sections, eg negative diversions.
// Just quote the whole protected macro invocation and use that as the single arg to SI.
// SI is most important for macros with unwanted side effects. such as defining other macros.
m4_define(`SI', `m4_ifelse(`$#', 0, ``$0'', `m4_ifelse(m4_eval(m4_divnum >= 0), 1, `$1')')')

// This is from the M4 examples, it helps with testing.
// forloop(var, from, to, stmt) - simple version
m4_define(`forloop', `m4_pushdef(`$1', `$2')_forloop($@)m4_popdef(`$1')')
m4_define(`_forloop',
       `$4`'m4_ifelse($1, `$3', `', `m4_define(`$1', m4_incr($1))$0($@)')')
m4_divert`'m4_dnl

cpp_test.m4:

This is a stretchy test file:

// A m4 test file for the cpp macros.
m4_include(`cpp_macros.m4')
// Thrid Line
m4_define(`VAL', 2)_
DEFINED(`VAL') DEFINED(`COMPLEX')
m4_define(`X', `bar = (Not True and falSE or True * true) + 3 ^ 2 <> 7 = foo')_
X
// Here we convert from another notation to m4.
// These two stansas are equivelent.
_ m4_define(`X', m4_translit(X, `a-z', `A-Z'))_ some random text
_ m4_define(`X', m4_patsubst(X, `TRUE', `1'))_
_ m4_define(`X', m4_patsubst(X, `FALSE', `0'))_
_ m4_define(`X', m4_patsubst(X, `NOT', `!'))_
_ m4_define(`X', m4_patsubst(X, `AND', `&&'))_
_ m4_define(`X', m4_patsubst(X, `OR', `||'))_
_ m4_define(`X', m4_patsubst(X, `\^', `**'))_
_ m4_define(`X', m4_patsubst(X, `\([^!=]\)=\([^=]\)', `\1==\2'))_
_ m4_define(`X', m4_patsubst(X, `\([^!=]\)=\([^=]\)', `\1==\2'))_
_ m4_define(`X', m4_patsubst(X, `<>', `!='))_
_
_
m4_define(`X', m4_patsubst(m4_patsubst(m4_patsubst(m4_patsubst(
    m4_patsubst(m4_patsubst(m4_patsubst(m4_patsubst(m4_translit(
    X, `a-z', `A-Z'), `TRUE', `1'), `FALSE', `0'),
    `NOT', `!'), `AND', `&&'), `OR', `||'), `\^', `**'),
    `\([^!=]\)=\([^=]\)', `\1==\2'), `<>', `!='))_
********
X
*******

m4_eval( 0+ (1 + 5) * 3 - VAL)
no blank line...
EXPREVAL = _EXPREVAL(`0')
CPP_STACK = __CPP_STACK__ m4_eval((__CPP_STACK__ < 0) || (_EXPREVAL(`1*2') == 0))
IF(true and  false or  false+0)
It is non zero
SI(`COMPLEX,VAL')
ELSE
It was Zero
ENDIF

IFDEF(`VAL')

It was defined.
ELSE
It was NOT defined.
ENDIF
***
m4_define(`NUM', 0)_
forloop(`NUM', 0, 3,
`IF(NUM == 1)
The if portion `NUM' = NUM
ELIF(NUM ==2)
The 1st elif portion `NUM' = NUM
ELIF(NUM ==2)
Should never get here, `NUM' = NUM
ELIF(NUM ==3)
The 2nd elif portion `NUM' = NUM
ELSE
The else portion `NUM' = NUM
ENDIF
')_
COMPLEX, VAL, NUM

How to run:

m4 -P ccp_test.m4

-- ClifCox - 07 Apr 2021

This topic: Sites > WebHome > M4Macros
Topic revision: 08 Apr 2021, ClifCox
This site is powered by FoswikiCopyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding Foswiki? Send feedback