Many interpreters and compilers are written in the CeeLanguage, instead of specialized grammars like EBNF.
C requires considerable code redundancy. This redundancy can be reduced by using macros and other CeePreprocessorStatements.
/* This example comes up often in interpreters */
#define Func(name, OP) name(int a, int b) { return a OP b; }
/* we can call the macros with very simple code */
Func(add, +)
Func(sub, -)
/* It will expand to code like: */
/* add(int a, int b) { return a + b); }
sub(int a, int b) { return a - b); }
*/
This is a
CodeGeneration technique.
- It reduces trivial code to an underlying pattern.
- It is a different work-around for the code duplication mentioned in the first 2 "accepted ways to use CeePreprocessorStatements".
- It may seem weird if you've never written an interpreter; but it is very handy for writing interpreters, and the alternative is not pretty, it's just ugly clutter.
- This technique will not be immediately clear to many C programmers. It will cause problems for them.
- It may be easier for a maintenance programmer to grasp if it is done in the same file as it is used, not in a separate header file.
- It lets you implement GenericProgramming in C, using the only real tool that C offers for GenericProgramming. GenericProgramming is widely considered to be one of the best parts of other languages, like C++ templates, Lisp macros, etc.
- It dramatically shortens the code. In some cases, 3000 lines of code can be boiled down to a 200 line file of trivial definitions.
- It follows OnceAndOnlyOnce, and thereby simplifies code maintenance.
- This approach leads to better maintainability. The alternative (in C) is CopyAndPasteProgramming, where every function is identical except for one or two characters (the operator "-" or "*" etc).
- It makes it easy to add features. A programmer can take an integer-only interpreter, and add working support for floating point by adding a single new 1-line macro (and of course some trivial data structures, et cetera). That's nothing to sneeze at.
- Similarly with other things here...they may be CodeSmell, but just as with gotos, there are times when it's the right thing to do.
Brian Kernighan wrote a paper about an awk to C translator he wrote(
http://cm.bell-labs.com/cm/cs/who/bwk/awkc++.ps). At one point he said "The thought of adding another type and another 75 functions to manage it was more than I could bear." This is the phenomenon that I'm claiming this macro approach solves (actually it turns out to be
MetaMacros and
CodeGeneration, but I'll skip that for the moment). Using the approach I am advocating, he wouldn't have to manage another 75 functions, it would happen automatically.
One way to avoid using macros like this is to switch to a different language. Interpreters and compilers can be written using special grammars. One can express a grammar cleanly using a syntax specifically designed for that purpose, rather than define a new syntax via C macros. EBNF (Extended BackusNaurForm) and the ANTLR (AntlrTranslatorGenerator) meta-language are well documented. The grammars and Java code are pretty and easy to understand. Lots of folks know them.
CeePreprocessorStatements, ExtensibleProgrammingLanguage