Macros

String Concatination, Redefinition, Tables, Examples


Macros are built on the #define preprocessor.

Normally a #define would look like:

	#define PI 3.142

But, a macro might look more like this.

	#define SQUARE(x) ( (x)*(x) )

The main difference is that the first example is a constant and the second is an expression. If the macro above was used in some code it may look like this:

        #define SQUARE(x) ( (x)*(x) )

        main()
        {
          int value=3;
          printf("%d \n", SQUARE(value));
        }

After preprocessing the code would become:

        main()
        {
          int value=3;
          printf("%d \n", value*value);
        }

It's generally a good idea to use extra parentheses when using complex macros. Notice that in the above example, the variable "x" is always within it's own set of parentheses. This way, it will be evaluated in whole, before being multiplied. If we used #define SQUARE(x) x*x then SQUARE(a + 3) would result in a + 3 * a + 3 which is evaluated as 3*a + a + 3 and NOT as (a + 3) * (a + 3)

Also, the entire macro is surrounded by parentheses, to prevent it from being contaminated by other code. If you're not careful, you run the risk of having the compiler misinterpret your code.

#, ##

The # and ## operators are used with the #define macro. Using # causes the first argument after the # to be returned as a string in quotes. Using ## concatenates what's before the ## with what's after it.

Example code:

For example, the command

#define to_string( s ) # s

will make the compiler turn this command

cout << to_string( Hello World! ) << endl;

into

cout << "Hello World!" << endl;

Here is an example of the ## command:

#define concatenate( x, y ) x ## y
...
int xy = 10;
...

This code will make the compiler turn

cout << concatenate( x, y ) << endl;

into

cout << xy << endl;

which will, of course, display '10' to standard output.

Redefinition

Macros can be redefined by first un-defining them. e.g.

#define SOMETHING 1
//any reference to SOMETHING here will be 1
#undef SOMETHING
#define SOMETHING 2
//any reference to SOMETHING here will be 2

Tables

see this code: https://github.com/CCurl/c5/blob/main/c5.c for an excellent use of macros to organize data in a table, and then extract different columns and use them in different ways by redefinition of the macro.

First, the code defines a "table" of data (primitives in the Forth language in this case) by calling a macro "X" which doesn't exist yet.

#define PRIMS \
	X(DUP,     "dup",       0, t=TOS; push(t); ) \
	X(SWAP,    "swap",      0, t=TOS; TOS=NOS; NOS=t; ) \
	X(DROP,    "drop",      0, pop(); ) \
	X(OVER,    "over",      0, t=NOS; push(t); ) \
	X(FET,     "@",         0, TOS = fetchCell((byte*)TOS); ) \
...

Note the use of \ to continue past the end of each line.

Now, we can decide which comma separated entry in each line we wish to use, by defining the X macro. e.g. if we want an enum of all the primitive symbols:

#define X(op, name, imm, cod) op,

enum _PRIM  {
	STOP, LIT1, LIT2, LIT4, CALL, JMP, JMPZ, NJMPZ, JMPNZ, NJMPNZ, PRIMS
};

In this case, we define a few special cases before we invoke the PRIMS which will then be expanded by the current X macro, which, although it takes in all 4 columns, only expands the "op" or first column, which is the symbols.

Later in the code, we can use this to help us build a switch to process each entry in the table. (I've changed this code from the original to clarify its function)

#undef X
#define X(op, name, imm, code) case op: code

void exec_opcodes(cell start) {
	byte *pc = (byte *)start;
next:
	if (pc==0) return;
	switch(*(pc++)) {
		case  STOP:   return;
		case  LIT1:   push((byte)*(pc++));
... //we can define a few manualy, or just invoke PRIMS to use the table:
		PRIMS
		default:
			printf("unknown opcode\n");
			return; // goto next;
	}
}


Examples:

macro example.


Notes:


See Also:


Top Master Index Keywords Functions


Martin Leslie