Phantasmal MUD Lib for DGD

Phantasmal Site > DGD > DGD Reference Manual > LPC > The Preprocessor

The Preprocessor

The preprocessor isn't a part of the LPC language proper. It's a special process that is run before the actual compilation of the program occurs. It can be seen as a very smart string translator. Specified strings in the code are replaced by other strings.

All preprocessor directives are given as strings starting with the character # on the first non-whitespace column of the line. It's considered good practice to put preprocessor directives on the very far left of the code, with the # in the very first column.

The #include statement

This is by far the most common preprocessor command. It simply tells the preprocessor to replace that line with the contents of an entire other file before going any further.

Data you put in included files is usually data that won't ever change and that you'll be referencing in several files. Instead of having to copy and paste the same lines into multiple places and maintaining multiple copies, you simply collect that data in an include file and include it in the program files as appropriate. Included file names traditionally end in .h.

The syntax for inclusion is simple:

#include <standard_file>
#include "special_file"
    

Note the absence of a ; after the line.

There are two different ways to write this. Which you use depends on where the file is that you want to include. There are a usually standard include files which may be in any of several different directories. Rather than having to remember exactly where they are, you can just give the name of the file you want to include if it's in a standard include directory.

#include <limits.h>
#include <types.h>
    

If you want to include files that aren't in the standard include path, for example files of your own, you have to specify where they are. You can do that either relative to the position of the file that uses it or by an absolute path. (NOTE: does DGD allow non-absolute include paths?)

#include "/d/Genesis/login/login.h"
#include "my_defs.h"
#include "/sys/adverbs.h"     /* Same as the shorter one above */
    

When you include standard files, always use the <>-path notation. Not only is it shorter and easier to distinguish, but also if the files move around then files included with relative or absolute syntax won't be found. If you use the special include syntax then they will be found in any standard directory, even if they move between them.

It's possible to include LPC files -- entire files full of code. Doing so is normally considered very bad form. Error handling usually has a bad time tracing errors in included files -- there are frequently problems with line numbers. Since you include the uncompiled code into several different objets, you will waste memory and CPU for these identical functions and variables. It also tends to be harder to figure out where functions are defined.

What does the extension of the file name really have to do with the contents then? Technically, nothing at all. But the convention is to keep functions in .cc files and definitions in .h files. Many mudlibs enforce this, and accept only certain file suffixes for compilation or other tasks.

The #define statement

#define is a very powerful macro or preprocessor command that can be abused endlessly. It's recommended that you use it with caution and only for simple tasks. Using it for complex tasks tends to make reading or debugging your code quite difficult.

The syntax is as follows:

#define <pattern> <substitute pattern>
#undef <pattern>
    

Any text in the file that matches <pattern> will be replaced with <substitute pattern> before compilation occurs. A #define is valid from the line it is found on until the end of the file or an #undef command that removes it.

Although the preprocessor allows #define labels to be any sort of legal text, it is customary to use only capital letters. This is so that they will be easily distinguishable as what they are.

Place all defines in the beginning of the file. Again, the compiler doesn't enforce this but it's a very good idea. It guarantees not only that they are easy to find, but also that they're usable throughout the file.

Common defines include paths, names and above all constants of any kind. By defining them, you avoid writing them over and over.

#define MAX_LOGIN  100          /* Max logged on players */
#define MY_USER    "/usr/System/my_user" /* My user object */
#define GREET_TEXT "Welcome!"   /* The login message */
    

Anywhere the pattern strings above occur in the file where they're defined (and any file that includes it), they will be replaced by the defined value above. The substitution includes the comments above, but they're ignored by the compiler.

DRIVER->message(GREET_TEXT + "\n");
    

If a macro extends beyond the end of the line you can terminate the lines with a \. That continues the macro onto the next line. However, you must put the newline immediately after the \. There may not be spaces or other characters between the backslash and the newline.

#define LONG_DEFINE  "beginning of string \
                      and end of the same" 
    

Function-like defines are fairly common and often abused. It's important to write macros such that every argument to the macro is enclosed in parenthesis where it's used. If you don't do that you can end up with some very strange results.

1: #define MUL_IT(a, b) a * b        /* Wrong */
2: #define MUL_IT(a, b) (a * b)      /* Not enough */
3: #define MUL_IT(a, b) ((a) * (b))  /* Correct */
    

What's the big difference? Look at this example:

result = MUL_IT(2 + 3, 4 * 5) / 5;

   Expanded with the three different macros, this becomes:

1: result = 2 + 3 * 4 * 5 / 5;       /* = 14, Wrong */
2: result = (2 + 3 * 4 * 5) / 5      /* = 12, Still wrong */
3: result = ((2 + 3) * (4 * 5)) / 5  /* = 20, Correct! */
    

Common problems with defined constants and functions include badly formulated macros, complicated macros used inside other macros (making the code almost impossible to understand) or humongous arrays or mappings in defines that are used often. The basic rule is to keep macros short and fairly simple.

The #if, #ifdef, #ifndef, #else and #elseif statements

The commands above are all preprocessor directives aimed at selecting certain parts of code and perhaps removing others depending on the state of a preprocessor variable.

The #if statement looks very much like a normal if statement, but is written a bit differently.

Assume you may have one of the following definitions somewhere:

#define code_VAR  2

or
        
#define code_VAR  3
    

Then you can write

#if code_VAR == 2
    <code that will be kept only if code_VAR == 2>
#else
    <code that will be kept only if code_VAR != 2>
#endif
    

You don't have to have the #else statement there at all if you don't want to. You can simply use #if and #endif.

It's sufficient to have the following statement to 'define' a preprocessor pattern as existing:

#define code_VAR    /* This defines the existance of code_VAR */
    

Then you can use #ifdef to check for its existence, like this:

#ifdef code_VAR
    <code that will be kept only if code_VAR is defined>
#else
    <code that will be kept only if code_VAR isn't defined>
#endif

or you can use #ifndef, which is essentially an #ifdef with an extra "not" implied.

#ifndef code_VAR
    <code that will be kept only if code_VAR isn't defined>
#else
    <code that will be kept only if code_VAR is defined>
#endif
    

Again, the #else is optional.

The #if/#ifdef/#ifndef preprocessor commands are frequently used to add debug code that you don't want to have activated all of the time, or code that will work differently depending on other very rarely changing parameters. Since the conditions have to be hard-coded in the file and can't change easily at runtime, most features will use a regular if instead of an #if.

Comments

Comments may seem like an odd thing to start with, but they're everywhere so you need to be able to recognize them from the very start. They're also important, so you need to know how to write them from the very start.

In some LPC dialects, there are two kinds of comments:

<code> // This is a comment stretching to the end of the line.
       // NOT SUPPORTED BY DGD!

<code> /* This is an enclosed comment */ <more code>
    
The first type of comment starts off with the // characters and then stretches all the way to the end of the line. If you want more lines of comments, you'll have to start off those as well with new // characters. This is like C++ single-line comments, and DGD does not support them!

The second type has a definite length. Those comments start with /* and end with */. They are useful when you have to write something that will stretch over several lines. You only have to write the comment symbol at then beginning and end, and not for every line in between. These are just like standard C comments.

Please note that the /* */ comment can not be nested. It is not correct to write something like this:

/* A comment
   /* A nested comment */
   the first continues
 */
    
What will happen is that the comment will end with the first */ found, leaving the text the first continues */ to be interpreted as if it was LPC code. That's not valid LPC code, so instead you'll get an error.