CSCI 485 Lab 2: the C pre-processor and macro programming

The objectives for this lab are (1) to get an understanding of the power of preprocessor commands and macro programming in C-like languages, and (2) to make use of C macros to extensively re-write our C source code at compile time.
(Assignment 2 involves applying these ideas.

Two lab sessions (Feb 2nd and 9th) have been allocated for working through lab 2 and beginning your work on assignment 2, which is due on February 13th.

Lab 2 part A: intro to the C preprocessor/macros

The first half of lab 2 will focus on working with the core features of the C preprocessor and macros, with an emphasis on the basic transformation aspects applicable to assignment 2 (implementing dimento features through C macros).

An overview of the basic syntax and use of C preprocessor directives is given here, and some macro examples here.

In terms of the dimento features, most can be created with relatively simple #defines, some relevant suggestions are included below.


Lab 2 part B: implementing dimento function declarations

The second half of lab 2 will focus on iterative and recursive C macros - understanding how they work and how they can be applied to the function declaration feature (func) of dimento.

The core of the problem with function declarations is that we want to transform something like func(fname,x,y,z) into something like
float fname(float x, float y, float z)

The "float fname(" part at the beginning, and the ")" at the end should be easy by the time you've finished part A above, the real trick is getting the "float " before the first parameter and ", float " before each of the remaining parameters.

If we try to replace the pattern func(fname,...), we can use __VA_ARGS__ to get the list of variables, but somehow we need to then rewrite the __VA_ARGS__ content, inserting the floats and commas in the right spots.

As mentioned in the lectures, true recursion is not supported in macro expansion, we have to provide a fixed set of rules to handle the different sequences of substitutions we wish to perform. Fortunately, the dimento specs say the number of parameters supported is strictly 1 to 6, so a fixed rule set is possible.

The general layout might follow the style below

/* rules to handle a specific number of args, 1-3, where at each
   step we process one argument then "recursing" for the rest,
   PROCESS_ARGi would be replaced with whatever you want to
   do to that one argument
   The initial call from processAll makes sure it starts with
   the correct doArgsi */
#define doArgs1(arg1     )  PROCESS_LAST
#define doArgs2(arg2, ...)  PROCESS_SECOND_TO_LAST, doArgs1(__VA_ARGS__)
#define doArgs3(arg3, ...)  PROCESS_THIRD_TO_LAST, doArgs2(__VA_ARGS__)

/ * count the number of arguments in a list, e.g. COUNT(x,y,z)
       works on up to three arguments
    Explanation:
    The actual arguments passed match up with positions _1, _2, etc,
        which are discarded (that's what the _x means in C macros).
    Then (when it runs out of passed arguments) the values 3, 2, 1, 0
       match up with the remaining _i's to discard in RESOLVE_COUNT.
    The value that matches up with the N in RESOLVE_COUNT is the
        number of arguments that were in the original list!  */
#define RESOLVE_COUNT(_1, _2, _3, N, ...) N
#define COUNT(...) RESOLVE_COUNT(__VA_ARGS__, 3, 2, 1, 0)

/* given a count of the number of args, build the name of
   the right doArgs variant and call it */
#define processCall(count, ...) doArgs##count(__VA_ARGS__)
#define processAll(count, ...) processCall(count, __VA_ARGS__)

/* top level call to process a list of args
#define process(...) processAll(COUNT(__VA_ARGS__), __VA_ARGS__)

/* An example of how it would work, showing the sequence of expansions
   process(x,y)
   processAll(COUNT(x,y), x,y)
   processAll(RESOLVE_COUNT(x,y,3,2,1,0),x, y)
   processAll(2,x,y)
   processCall(2,x,y)
   doArgs2(x,y)
   PROCESS_SECOND_TO_LAST doArgs1(y)
   PROCESS_SECOND_TO_LAST PROCESS_LAST
 */

// example
#define doArgs1(arg1     )  foo(arg1)
#define doArgs2(arg2, ...)  blah(arg2), doArgs1(__VA_ARGS__)
#define doArgs3(arg3, ...)  ick(arg3), doArgs2(__VA_ARGS__)
#define RESOLVE_COUNT(_1, _2, _3, N, ...) N
#define COUNT(...) RESOLVE_COUNT(__VA_ARGS__, 3, 2, 1, 0)
#define processCall(count, ...) doArgs##count(__VA_ARGS__)
#define processAll(count, ...) processCall(count, __VA_ARGS__)
#define process(...) processAll(COUNT(__VA_ARGS__), __VA_ARGS__)
process(x,y) // becomes blah(x), foo(y)


SAMPLE RESULTS

Original dimento code
#include "dimento.h"
#include <math.h>

func(root,a) {
   return sqrt(a);
}

func(f,a,b) {
   decl(x,y,z);
   x = a * a;
   y = b * b;
   z = eval(sqrt,x / y);
   return z;
}

func(product,a,b,c) {
   return a * b * c;
}

dimento_start

   decl(x);
   decl(y,z);
   x = 3;
   y = 35;
   z = eval(f,x,y);
   prt(z);
   prt(z * if(10,x,y,z));
   decl(a,b,c);
   a = AND(x,z);
   prt(a);
   b = OR(x,z);
   prt(b);
   c = NOT(x);
   prt(c);
   a = test(b>c);
   prt(a);
   a = 5;
   while (a,true) {
     a--; prt(a);
   }

dimento_end


Expanded C code (minus the mass of #include stuff)

float root( float a ) {
   return sqrt(a);
}

float f( float a, float b ) {
   float x,y,z ;
   x = a * a;
   y = b * b;
   z = sqrt(x / y);
   return z;
}

float product( float a, float b, float c ) {
   return a * b * c;
}

int main () {

   float x ;
   float y,z ;
   x = 3;
   y = 35;
   z = f(x,y);
   printf("%s = %g\n", "z", (z));
   printf("%s = %g\n", "z * if(10,x,y,z)", (z * (((10)<0)?(x):(((10)>0)?(z):(y)))));
   float a,b,c ;
   a = (((x)<(z))?((((x)<0)?-1:((x)>0))):((((z)<0)?-1:((z)>0))));
   printf("%s = %g\n", "a", (a));
   b = (((x)>(z))?((((x)<0)?-1:((x)>0))):((((z)<0)?-1:((z)>0))));
   printf("%s = %g\n", "b", (b));
   c = (-((((x)<0)?-1:((x)>0))));
   printf("%s = %g\n", "c", (c));
   a = (((b>c)<0)?-1:((b>c)>0));
   printf("%s = %g\n", "a", (a));
   a = 5;
   while ((((a)<0)?-1:((a)>0)) == (1)) {
     a--; printf("%s = %g\n", "a", (a));
   }

}
Output from running the C code
z = 0.0857143
z * if(10,x,y,z) = 0.00734694
a = 1
b = 1
c = -1
a = 1
a = 4
a = 3
a = 2
a = 1
a = 0