MG Mud User | 88f1247 | 2016-06-24 23:31:02 +0200 | [diff] [blame^] | 1 | Intermediate LPC |
| 2 | Descartes of Borg |
| 3 | November 1993 |
| 4 | |
| 5 | Chapter 4: The LPC Pre-Compiler |
| 6 | |
| 7 | 4.1 Review |
| 8 | The previous chapter was quite heavy, so now I will slow down a bit so |
| 9 | you can digest and play with mappings and arrays by taking on the |
| 10 | rather simple topic of the LPC pre-compiler. By this point, however, |
| 11 | you should well understand how the driver interacts with the mudlib and |
| 12 | be able to code objects which use call outs and heart beats. In addition, |
| 13 | you should be coding simple objects which use mappings and arrays, |
| 14 | noting how these data types perform in objects. It is also a good idea to |
| 15 | start looking in detail at the actual mudlib code that makes up your mud. |
| 16 | See if you understand everything which is going on in your mudlibs |
| 17 | room and monster codes. For things you do not understand, ask the |
| 18 | people on your mud designated to answer creator coding questions. |
| 19 | |
| 20 | Pre-compiler is actually a bit of a misnomer since LPC code is never |
| 21 | truly compiled. Although this is changing with prototypes of newer |
| 22 | LPC drivers, LPC drivers interpret the LPC code written by creators |
| 23 | rather than compile it into binary format. Nevertheless, the LPC pre- |
| 24 | compiler functions still perform much like pre-compilers for compiled |
| 25 | languages in that pre-compiler directives are interpreted before the driver |
| 26 | even starts to look at object code. |
| 27 | |
| 28 | 4.2 Pre-compiler Directives |
| 29 | If you do not know what a pre-compiler is, you really do not need to |
| 30 | worry. With respect to LPC, it is basically a process which happens |
| 31 | before the driver begins to interpret LPC code which allows you to |
| 32 | perform actions upon the entire code found in your file. Since the code |
| 33 | is not yet interpreted, the pre-compiler process is involved before the file |
| 34 | exists as an object and before any LPC functions or instructions are ever |
| 35 | examined. The pre-compiler is thus working at the file level, meaning |
| 36 | that it does not deal with any code in inherited files. |
| 37 | |
| 38 | The pre-compiler searches a file sent to it for pre-compiler directives. |
| 39 | These are little instructions in the file meant only for the pre-compiler |
| 40 | and are not really part of the LPC language. A pre-compiler directive is |
| 41 | any line in a file beginning with a pound (#) sign. Pre-compiler |
| 42 | directives are generally used to construct what the final code of a file will |
| 43 | look at. The most common pre-compiler directives are: |
| 44 | |
| 45 | #define |
| 46 | #undefine |
| 47 | #include |
| 48 | #ifdef |
| 49 | #ifndef |
| 50 | #if |
| 51 | #elseif |
| 52 | #else |
| 53 | #endif |
| 54 | #pragma |
| 55 | |
| 56 | Most realm coders on muds use exclusively the directives #define and |
| 57 | #include. The other directives you may see often and should understand |
| 58 | what they mean even if you never use them. |
| 59 | |
| 60 | The first pair of directives are: |
| 61 | #define |
| 62 | #undefine |
| 63 | |
| 64 | The #define directive sets up a set of characters which will be replaced |
| 65 | any where they exist in the code at precompiler time with their definition. |
| 66 | For example, take: |
| 67 | |
| 68 | #define OB_USER "/std/user" |
| 69 | |
| 70 | This directive has the pre-compiler search the entire file for instances of |
| 71 | OB_USER. Everywhere it sees OB_USER, it replaces with "/std/user". |
| 72 | <* NOTE Highlander@MorgenGrauen 11.2.94: |
| 73 | WRONG. OB_USER will _not_ be replaced if within "" in which case it is |
| 74 | treated as a normal string. So it is possible to write the text OB_USER. |
| 75 | *> |
| 76 | Note that it does not make OB_USER a variable in the code. The LPC |
| 77 | interpreter never sees the OB_USER label. As stated above, the pre- |
| 78 | compiler is a process which takes place before code interpretation. So |
| 79 | what you wrote as: |
| 80 | |
| 81 | #define OB_USER "/std/user" |
| 82 | |
| 83 | void create() { |
| 84 | if(!file_exists(OB_USER+".c")) write("Merde! No user file!"); |
| 85 | else write("Good! User file still exists!"); |
| 86 | } |
| 87 | |
| 88 | would arrive at the LPC interpreter as: |
| 89 | |
| 90 | void create() { |
| 91 | if(!file_exists("/std/user"+".c")) write("Merde! No user file!"); |
| 92 | else write("Good! User file still exists!"); |
| 93 | } |
| 94 | |
| 95 | <* NOTE Highlander@MorgenGrauen 11.2.94 |
| 96 | But: write("Text is OB_USER foo bar\n"); |
| 97 | simply writes "Text is OB_USER foo bar". Confer previous note. |
| 98 | *> |
| 99 | |
| 100 | Simply put, #define just literally replaces the defined label with whatever |
| 101 | follows it. You may also use #define in a special instance where no |
| 102 | value follows. This is called a binary definition. For example: |
| 103 | |
| 104 | #define __NIGHTMARE |
| 105 | |
| 106 | exists in the config file for the Nightmare Mudlib. This allows for pre- |
| 107 | compiler tests which will be described later in the chapter. |
| 108 | |
| 109 | The other pre-compiler directive you are likely to use often is #include. |
| 110 | As the name implies, #include includes the contents of another file right |
| 111 | into the file being pre-compiled at the point in the file where the directive |
| 112 | is placed. Files made for inclusion into other files are often called header |
| 113 | files. They sometimes contain things like #define directives used by |
| 114 | multiple files and function declarations for the file. The traditional file |
| 115 | extension to header files is .h. |
| 116 | |
| 117 | Include directives follow one of 2 syntax's: |
| 118 | |
| 119 | #include <filename> |
| 120 | #include "filename" |
| 121 | |
| 122 | If you give the absolute name of the file, then which syntax you use is |
| 123 | irrelevant. How you enclose the file name determines how the pre- |
| 124 | compiler searches for the header files. The pre-compiler first searches in |
| 125 | system include directories for files enclosed in <>. For files enclosed in |
| 126 | "", the pre-compiler begins its search in the same directory as the file |
| 127 | going through the pre-compiler. Either way, the pre-compiler will |
| 128 | search the system include directories and the directory of the file for the |
| 129 | header file before giving up. The syntax simply determines the order. |
| 130 | <* NOTE Highlander@MorgenGrauen 11.2.94 |
| 131 | When using standard-headerfiles one should choose <>. "" is appropriate |
| 132 | when dealing with selfdefined headerfiles. |
| 133 | *> |
| 134 | |
| 135 | The simplest pre-compiler directive is the #pragma directive. It is |
| 136 | doubtful you will ever use this one. Basically, you follow the directive |
| 137 | with some keyword which is meaningful to your driver. The only |
| 138 | keyword I have ever seen is strict_types, which simply lets the driver |
| 139 | know you want this file interpreted with strict data typing. I doubt you |
| 140 | will ever need to use this, and you may never even see it. I just included |
| 141 | it in the list in the event you do see it so you do not think it is doing |
| 142 | anything truly meaningful. |
| 143 | |
| 144 | The final group of pre-compiler directives are the conditional pre- |
| 145 | compiler directives. They allow you to pre-compile the file one way |
| 146 | given the truth value of an expression, otherwise pre-compile the file |
| 147 | another way. This is mostly useful for making code portable among |
| 148 | mudlibs, since putting the m_delete() efun in code on a MudOS mud |
| 149 | would normally cause an error, for example. So you might write the |
| 150 | following: |
| 151 | |
| 152 | #ifdef MUDOS |
| 153 | map_delete(map, key); |
| 154 | #else |
| 155 | map = m_delete(map, key); |
| 156 | #endif |
| 157 | |
| 158 | which after being passed through the pre-compiler will appear to the |
| 159 | interpreter as: |
| 160 | |
| 161 | map_delete(map, key); |
| 162 | |
| 163 | on a MudOS mud, and: |
| 164 | |
| 165 | map = m_delete(map, key); |
| 166 | |
| 167 | on other muds. The interpreter never sees the function call that would |
| 168 | cause it to spam out in error. |
| 169 | |
| 170 | Notice that my example made use of a binary definition as described |
| 171 | above. Binary definitions allow you to pass certain code to the |
| 172 | interpreter based on what driver or mudlib you are using, among other |
| 173 | conditions. |
| 174 | |
| 175 | 4.3 Summary |
| 176 | The pre-compiler is a useful LPC tool for maintaining modularity among |
| 177 | your programs. When you have values that might be subject to change, |
| 178 | but are used widely throughout your files, you might stick all of those |
| 179 | values in a header file as #define statements so that any need to make a |
| 180 | future change will cause you to need to change just the #define directive. |
| 181 | A very good example of where this would be useful would be a header |
| 182 | file called money.h which includes the directive: |
| 183 | #define HARD_CURRENCIES ({ "gold", "platinum", "silver", |
| 184 | "electrum", "copper" }) |
| 185 | so that if ever you wanted to add a new hard currency, you only need |
| 186 | change this directive in order to update all files needing to know what the |
| 187 | hard currencies are. |
| 188 | |
| 189 | The LPC pre-compiler also allows you to write code which can be |
| 190 | ported without change among different mudlibs and drivers. Finally, |
| 191 | you should be aware that the pre-compiler only accepts lines ending in |
| 192 | carriage returns. If you want a multiple line pre-compiler directive, you |
| 193 | need to end each incomplete line with a backslash(\). |
| 194 | |
| 195 | Copyright (c) George Reese 1993 |