| Intermediate LPC |
| Descartes of Borg |
| November 1993 |
| |
| Chapter 4: The LPC Pre-Compiler |
| |
| 4.1 Review |
| The previous chapter was quite heavy, so now I will slow down a bit so |
| you can digest and play with mappings and arrays by taking on the |
| rather simple topic of the LPC pre-compiler. By this point, however, |
| you should well understand how the driver interacts with the mudlib and |
| be able to code objects which use call outs and heart beats. In addition, |
| you should be coding simple objects which use mappings and arrays, |
| noting how these data types perform in objects. It is also a good idea to |
| start looking in detail at the actual mudlib code that makes up your mud. |
| See if you understand everything which is going on in your mudlibs |
| room and monster codes. For things you do not understand, ask the |
| people on your mud designated to answer creator coding questions. |
| |
| Pre-compiler is actually a bit of a misnomer since LPC code is never |
| truly compiled. Although this is changing with prototypes of newer |
| LPC drivers, LPC drivers interpret the LPC code written by creators |
| rather than compile it into binary format. Nevertheless, the LPC pre- |
| compiler functions still perform much like pre-compilers for compiled |
| languages in that pre-compiler directives are interpreted before the driver |
| even starts to look at object code. |
| |
| 4.2 Pre-compiler Directives |
| If you do not know what a pre-compiler is, you really do not need to |
| worry. With respect to LPC, it is basically a process which happens |
| before the driver begins to interpret LPC code which allows you to |
| perform actions upon the entire code found in your file. Since the code |
| is not yet interpreted, the pre-compiler process is involved before the file |
| exists as an object and before any LPC functions or instructions are ever |
| examined. The pre-compiler is thus working at the file level, meaning |
| that it does not deal with any code in inherited files. |
| |
| The pre-compiler searches a file sent to it for pre-compiler directives. |
| These are little instructions in the file meant only for the pre-compiler |
| and are not really part of the LPC language. A pre-compiler directive is |
| any line in a file beginning with a pound (#) sign. Pre-compiler |
| directives are generally used to construct what the final code of a file will |
| look at. The most common pre-compiler directives are: |
| |
| #define |
| #undefine |
| #include |
| #ifdef |
| #ifndef |
| #if |
| #elseif |
| #else |
| #endif |
| #pragma |
| |
| Most realm coders on muds use exclusively the directives #define and |
| #include. The other directives you may see often and should understand |
| what they mean even if you never use them. |
| |
| The first pair of directives are: |
| #define |
| #undefine |
| |
| The #define directive sets up a set of characters which will be replaced |
| any where they exist in the code at precompiler time with their definition. |
| For example, take: |
| |
| #define OB_USER "/std/user" |
| |
| This directive has the pre-compiler search the entire file for instances of |
| OB_USER. Everywhere it sees OB_USER, it replaces with "/std/user". |
| <* NOTE Highlander@MorgenGrauen 11.2.94: |
| WRONG. OB_USER will _not_ be replaced if within "" in which case it is |
| treated as a normal string. So it is possible to write the text OB_USER. |
| *> |
| Note that it does not make OB_USER a variable in the code. The LPC |
| interpreter never sees the OB_USER label. As stated above, the pre- |
| compiler is a process which takes place before code interpretation. So |
| what you wrote as: |
| |
| #define OB_USER "/std/user" |
| |
| void create() { |
| if(!file_exists(OB_USER+".c")) write("Merde! No user file!"); |
| else write("Good! User file still exists!"); |
| } |
| |
| would arrive at the LPC interpreter as: |
| |
| void create() { |
| if(!file_exists("/std/user"+".c")) write("Merde! No user file!"); |
| else write("Good! User file still exists!"); |
| } |
| |
| <* NOTE Highlander@MorgenGrauen 11.2.94 |
| But: write("Text is OB_USER foo bar\n"); |
| simply writes "Text is OB_USER foo bar". Confer previous note. |
| *> |
| |
| Simply put, #define just literally replaces the defined label with whatever |
| follows it. You may also use #define in a special instance where no |
| value follows. This is called a binary definition. For example: |
| |
| #define __NIGHTMARE |
| |
| exists in the config file for the Nightmare Mudlib. This allows for pre- |
| compiler tests which will be described later in the chapter. |
| |
| The other pre-compiler directive you are likely to use often is #include. |
| As the name implies, #include includes the contents of another file right |
| into the file being pre-compiled at the point in the file where the directive |
| is placed. Files made for inclusion into other files are often called header |
| files. They sometimes contain things like #define directives used by |
| multiple files and function declarations for the file. The traditional file |
| extension to header files is .h. |
| |
| Include directives follow one of 2 syntax's: |
| |
| #include <filename> |
| #include "filename" |
| |
| If you give the absolute name of the file, then which syntax you use is |
| irrelevant. How you enclose the file name determines how the pre- |
| compiler searches for the header files. The pre-compiler first searches in |
| system include directories for files enclosed in <>. For files enclosed in |
| "", the pre-compiler begins its search in the same directory as the file |
| going through the pre-compiler. Either way, the pre-compiler will |
| search the system include directories and the directory of the file for the |
| header file before giving up. The syntax simply determines the order. |
| <* NOTE Highlander@MorgenGrauen 11.2.94 |
| When using standard-headerfiles one should choose <>. "" is appropriate |
| when dealing with selfdefined headerfiles. |
| *> |
| |
| The simplest pre-compiler directive is the #pragma directive. It is |
| doubtful you will ever use this one. Basically, you follow the directive |
| with some keyword which is meaningful to your driver. The only |
| keyword I have ever seen is strict_types, which simply lets the driver |
| know you want this file interpreted with strict data typing. I doubt you |
| will ever need to use this, and you may never even see it. I just included |
| it in the list in the event you do see it so you do not think it is doing |
| anything truly meaningful. |
| |
| The final group of pre-compiler directives are the conditional pre- |
| compiler directives. They allow you to pre-compile the file one way |
| given the truth value of an expression, otherwise pre-compile the file |
| another way. This is mostly useful for making code portable among |
| mudlibs, since putting the m_delete() efun in code on a MudOS mud |
| would normally cause an error, for example. So you might write the |
| following: |
| |
| #ifdef MUDOS |
| map_delete(map, key); |
| #else |
| map = m_delete(map, key); |
| #endif |
| |
| which after being passed through the pre-compiler will appear to the |
| interpreter as: |
| |
| map_delete(map, key); |
| |
| on a MudOS mud, and: |
| |
| map = m_delete(map, key); |
| |
| on other muds. The interpreter never sees the function call that would |
| cause it to spam out in error. |
| |
| Notice that my example made use of a binary definition as described |
| above. Binary definitions allow you to pass certain code to the |
| interpreter based on what driver or mudlib you are using, among other |
| conditions. |
| |
| 4.3 Summary |
| The pre-compiler is a useful LPC tool for maintaining modularity among |
| your programs. When you have values that might be subject to change, |
| but are used widely throughout your files, you might stick all of those |
| values in a header file as #define statements so that any need to make a |
| future change will cause you to need to change just the #define directive. |
| A very good example of where this would be useful would be a header |
| file called money.h which includes the directive: |
| #define HARD_CURRENCIES ({ "gold", "platinum", "silver", |
| "electrum", "copper" }) |
| so that if ever you wanted to add a new hard currency, you only need |
| change this directive in order to update all files needing to know what the |
| hard currencies are. |
| |
| The LPC pre-compiler also allows you to write code which can be |
| ported without change among different mudlibs and drivers. Finally, |
| you should be aware that the pre-compiler only accepts lines ending in |
| carriage returns. If you want a multiple line pre-compiler directive, you |
| need to end each incomplete line with a backslash(\). |
| |
| Copyright (c) George Reese 1993 |