Last update: April 19th, 1993

LPC

1 - Preface

This document is intended to help you to get started with coding in LPC more quickly. Many people who become wizard have never programmed before, and even if they have experience in that field, then usually with a language that differs substantially from LPC. In this document we will explain the syntax of LPC, and some other things that are usefull to know.

It could very well be that you miss certain parts in this document, or that you feel that important concepts are not explained where they should have. I urge you to let us know what you miss, and what changes you would like to see made. Most of the administrators are veteran coders, and to speak for myself, it is hard to picture what level of explanation is deep enough for a beginning user. To me, things might seem obvious, when they are in fact not at all that trivial. Mail an Arch, or "sysbug" it, and we will try to keep this document as helpfull as possible. Your help is greatly appreciated.

Thanks must go to Nick, who sparked the flame of my activity by putting together a compilation of the old LPC files, and who I am sure will try hard to keep this file as correct as possible.

Tricky

2 - Contents


  1. Preface
  2. Contents
  3. The LPMud world
  4. LPC
  5. How do you go about writing your own programs?
  6. Outline and indention
  7. The syntax
  8. Types
  9. Operators
  10. Statements
  11. Functions
  12. Inheritance
  13. Arrays
  14. Special statements
    1. if
    2. for
    3. while
    4. switch
  15. Comments
  16. The preprocessor
    1. #include
    2. #define, #undef
    3. #if, #ifdef, #ifndef

3 - The LPMud world


As we all know, the LPMud world (named after the initiator: Lars Pensj|'s Multi user dungeon) consists of many objects. We can pick them up, do things with them, we can walk around in them, heck, even we ourselves are objects in the world. The world is really a large quantity of little programs that are maintained by a big interpreter, or compiler as you wish, that runs on a computer somewhere. This interpreter is what people call "the gamedriver".

At startup, the gamedriver loads some initial objects, and then sets up a connection, waiting for people to log in. As people log in and walk around, they will encounter objects that have not been loaded yet. The gamedriver quickly loads them just before they get to see them, so it will look just as if the world always has been there. Sometimes this goes wrong; then you will get to see the famous "wrongness in the fabric of space" message.

All the code that the driver can load is called the mudlib. The mudlib are the routines that allow the world to be built. The things that you inherit, most of the functions you use, they comprise the mudlib.

4 - LPC


The programs that the gamedriver tries to load are written in the language called LPC. The C at the end is no coincidence. LPC is closely related to the language C. For those of you who start to cringe in terror: relax, it is less complicated than C. The syntax looks very much like the syntax of C. If you want better explanation about the syntax than that shown below, you might want to take a peek at Kernighan and Ritchie's "Programming in C", which is a very clear book on the language C.

5 - How do you go about writing your own programs?


First of all, you will want to write an object. You can write an object using the editor "ed", or you can use your favourite editor at home, and ftp your object to the site to which you usually telnet, using port 3001 Use your own login name and your own password. Once you are in, you can "put" the file in your own directory, or "get" one from it.

The ftp method seems to be more work, but to many people the use of their favourite editor outweighs those disadvantages. However, I also know people who wouldn't trade in "ed" for the world. Ah well, take your pick.

Once you have written your object, you can try to "load <filename>" from within the mud. You will probably get an error message, which you can examine with "errlog", or with "tail /lplog". The latter command shows all errors the gamedriver encountered, and if you are quick enough, your error will be on the bottom of what you get to see.

Once the object is bugfree, you can try to "clone <filename>". Cloning will give you a copy of the object that you have written. You can clone something more than once, resulting in multiple objects that are all alike. Cloning is exactly the same a loading, except that loading will not give you an object.

If you want to get rid of some of those copies, you can "destruct" them.

If you have loaded or cloned an object, the gamedriver will keep the code of that object in memory, so it doesn't have to go through the trouble of compiling it again next time you want to clone it. Consequently, if you have cloned something and you have changed the file, a renewed clone will still give you a copy of the same old object as before. The gamedriver should first be notified that it has to forget about the old code. To do this, there is the command "update <filename>". So if you first update the file, and then clone it, you will get the new and improved object.

You will find no examples of code in this document, you can find them in the INTRO doc. There you will also find a description of possible bugs you might encounter on your trip through LPC-country.

6 - Outline and indentation


Before we take a look at the language, let's mention that the gamedriver does not care how you write your code. You use tabs, spaces, newlines wherever you see fit to decorate your code. There are some exceptions, like strings. Strings always have two quotes '"', and those two quotes have to be on the same line. Of course you can use the linebreak character '\' to glue a few lines together, but that is the same as telling the gamedriver that those lines are in fact one line.

However, often other people also have to work with the code you have written. Readability of your code is greatly improved if you use the standard indentation rules, as they are followed in this document. The also is a command that will indent code for you, it is conveniently called "indent <filename>".

7 - The syntax


Enough beating around the bush. Let us get down with what we promised; describing the language. Like most languages, LPC consists of three main things:

Note that LPC is case sensitive; the functions foo_bar() and FoO_BaR() have different names.

8 - Types


LPC knows very few types, that is what keeps it from being complex. Here are all types LPC knows:

int
An integer, ranging from -2147483648 to 2147483657
status
A boolean, either 0 or 1 (same as int)
string
A string (not a pointer to a string), e.g. "Hello Mud!"
object
A pointer to an object
mapping
A fancy type of array, indexed on strings instead of on an integer index
mixed
Any type of variable (avoid this type when possible)
void
This type can be used for functions that return nothing.

From the above types, except for void, one can construct arrays as well by adding a '*' before the variable name.

All uninitialized variables will have the value 0. A pointer to a destructed object will also be 0.

Global variables can have a certain qualifier. 'static' Will prohibit the variable from being saved with save_object() or being destroyed by restore_object(). A global variable can also be 'private' which means that is is only addressable from within the inheritance block in which it is defined.

Examples:

	string name_str;
	int a, b, c;
	int *num_arr, *fib_arr = ({ 1, 1, 3, 5 }), x = 3;
	static object owner;
	private int foo = 12345;
	mapping Q = ([ "f":"foo", "b":"bar" ]);

9 - Operators


About the used notation:

var
is a variable of a certain type
expr
is an expression, i.e. another variable, a function, or a combination of the two with operators. You can use braces '(' and ')' to enclose an expression and to force the expression to be evaluated before the stuff outside the braces.

If you ever need truthfunctional values, LPC considers 0 to be false, and everything other than 0 is considered to be true. This goes for all types. If the variable is empty, it is considered to be not 0, and thus true. So the string "" is true, just like the array ({ }) and the mapping ([ ]).

These are the operators availailable in LPC. They are listed in the order or precedence (low priority first):

expr1, expr2
Evaluate 'expr1' and then 'expr2'. The returned value is the result of 'expr2'. The returned value of 'expr1' is thrown away.
var = expr
Evaluate 'expr', and assign the value to 'var'. The new value of 'var' is the result.
var += expr
Assign the value of 'var' + 'expr' to 'var'. This is equivalent to 'var = var + expr'. Warning! If the type of the variable is not 'int', then it should be initialized. Otherwise you will get an errormessage. The same goes for all other '?=' operators.
var -= expr
Equivalent to 'var = var - expr'
var &= expr
Equivalent to 'var = var & expr'
var |= expr
Equivalent to 'var = var | expr'
var ^= expr
Equivalent to 'var = var ^ expr'
var <<= expr
Equivalent to 'var = var << expr'
var >>= expr
Equivalent to 'var = var >> expr'
var *= expr
Equivalent to 'var = var * expr'
var %= expr
Equivalent to 'var = var % expr'
var /= expr
Equivalent to 'var = var / expr'
expr1 || expr2
The result is true if 'expr1' or 'expr2' is true. 'expr2' is not evaluated if 'expr1' was true.
expr1 && expr2
The result is true i 'expr1' and 'expr2' is true. 'expr2' is not evaluated if 'expr1' was false.
expr1 | expr2
The result is the bitwise or of 'expr1' and 'expr2'.
expr1 ^ expr2
The result is the bitwise xor of 'expr1' and 'expr2'.
expr1 & expr2
The result is the bitwise and of 'expr1' and 'expr2'.
expr1 == expr2
This compares 'expr1' with 'expr2', and returns true if they are equal. Notice the difference with 'expr1 = expr2'! This operation is valid for strings, integers and objects.
expr1 != expr2
The result is true if 'expr1' is not equal to 'expr2'. Valid for strings, integers and objects.
expr1 > expr2
The result is true if 'expr1' is greater than 'expr2'. Valid for integers, and also for strings, where the alphabetical order is checked, e.g. ("b" > "a") is true.
expr1 >= expr2
True if 'expr1' is greater than, or equal to 'expr2'. Valid for integers and strings.
expr1 < expr2
True if 'expr1' is smaller than 'expr2'. Valid for integers and strings.
expr1 <= expr2
True if 'expr1' is smaller than, or equal to 'expr2'. Valid for integers and strings.
expr1 << expr2
Shift 'expr1' left 'expr2' bits. The value returned is 'expr1', 'expr2' times multiplied with 2. Only valid for integers.
expr1 >> expr2
Shift 'expr1' right 'expr2' bits. The value returned is 'expr1'. 'expr2' times divided by 2. Only valid for integers.
expr1 + expr2
Add 'expr1' and 'expr2'. If both are integers, then arithmetic addition is used. If one of the expressions is a string, then that string is concatenated with the other value. If both are arrays, then the arrays are concatenated.
expr1 - expr2
Subtract 'expr2' from 'expr1'. If both are integers, then arithmetic subtraction is used. If both are arrays, then the result is 'expr1' with all (?) occurrences of 'expr2' removed from it.
expr1 * expr2
Multiply 'expr1' with 'expr2'. Only valid for integers.
expr1 % expr2
The modulo operator of the integer arguments. In other words, what remains after division of 'expr1' by 'expr2' is returned.
expr1 / expr2
Integer division; numbers behind the decimal point are discarded.
++var
Increase the value of variable 'var' by 1 first, then return the new value (preincrement).
--var
Decrease the value of variable 'var' by 1 first, then return the new value (predecrement).
-var
Return the negative value of 'var'.
!var
Compute the logical 'not' of a variable. Valid on all types. If 'var' was true, return false; if 'var' was false, return true.
~var
The boolean 'not' of an integer.
var++
Return the value 'var' first, then increase its value by 1 (postincrement).
var--
Return the value 'var' first, then decrease its value by 1 (postdecrement).
expr1[expr2]
The array given by 'expr1' is indexed by 'expr2'. If 'expr1' is an array, then 'expr2' should be an integer. If 'expr1' is a mapping, then 'expr2' should be a string.
expr1 ? expr2 : expr3
If 'expr1' is true, return the value of 'expr2'; else return the value of 'expr3'. Only 'expr1' and the chosen expression are evaluated. Valid for all types.
expr1->name(...)
'expr1' gives either an object or a string which is converted to an object, and calls the function 'name' in this object. Compare "call_other(expr1, name, ...)", which does the same.

10 - Statements


Statements in LPC have to be separated from each other with semicolons. Sometimes it is necessary to group a lot of statements together; in that case you construct a block.

A block is a special statement, that begins with '{', contains a list of statements, and ends with '}'. Variables defined inside the block are local to that block, and override variables with the same name from outside the block. The variables have to be defined at the beginning of the block, before every other statement of the block.

11 - Functions


A locally defined function can have any number of arguments. All basic types can be given as an argument. Unitialized arguments are set to 0.

As return value, functions can use the same types as variables. They can also have qualifiers to specify the scope of the function. A return value is sent with the 'return' statement, after which the execution of the function will also return to its caller. All data types except void can be used in the return statement. If the function is of the type 'void', simply "return;" is enough to return.

It is not mandatory to declare the type of a function, or the types of the arguments. If you use types, the gamedriver will be able to give more accurate error messages. However, the gamedriver will also be a lot pickier, and will complain if you use functions before you declare them. Let us suppose that on line 42 in our code, there is a call to the function "foo()". If the function is defined on line 57, the gamedriver will complain, because it does not know yet what the function looks like.

There are two ways to solve this:

It is illegal to have a function with the same name as a global function, or local variable. If there is no return statement, 0 will be returned.

These qualifiers are applicable to functions:

private
The function can only be called from within the same object.
static
The function can only be called from within the same object.
nomask
This function may not be redefined by other objects that inherit this object.
public
The function can be called by anyone from anywhere, and may also be redefined in other objects that inherit this one. This is the default qualifier.
varargs
The function may be called with a smaller number of arguments than specified.

The 'varargs' qualifier may be used together with any other qualifier.

This is what a function looks like:

	<qualifier> <type>
	function_name(argument1, argument2 ...)
	{
	    statements;
	    ...
	    return value;
	}

Note that the arguments that are received are copies of the originals that were supplied. You may assign new values to them if you like, but the calling function will not notice anything. This is not true for arrays, but fiddling around with the arguments is considered bad coding practice. If you want to return a changed array, simply return it and don't return nothing except a sneakily changed argument.

Functions in other objects can be called with 'obj->fun(arg, ...)'. Another way to get the same effect is 'call_other(ob, "fun", arg...)'. The first method looks more natural. Static functions cannot be called in other objects.

12 - Inheritance


An object can inherit all variables and functions from another object. This will save you from having to redefine the functions defined in the other object. You inherit other objects with the declaration 'inherit "file";'. This must come before any local variables or functions of this object. You may inherit more than one objects if you want to.

Functions of the inherited file can be called as if they were defined like other functions in the same object, unless they were qualified as 'private'.

Functions of the inherited object may be redefined, as long as they are not 'nomask'. It might occur that you still want to call the original of the redefined function, then you can call it by appending "::" in front of the name.

Here is a simple example, where you should keep in mind that the all the called functions are defined in the inherited object:

	inherit "/std/room";

	void
	create_room()
	{
	    set_name("Workroom");
	    set_long("This is the workroom of someone.\n");
	    add_exit("/d/Genesis/wiz/flame", "flame");
	}

	void
	init()
	{
	    ::init();
	    write("The workroom greets you.\n");
	}

13 - Arrays


There is support for arrays. An array can be declared by putting a '*' in front of the variable definition, like:

which would indicate an array of integers. The gamedriver will take care of the memory allocation for you.

The array constructors are '({' and '})'. Elements of an array are separated by commas and arrays can be added to or subtracted from each other. Elements can be of any type, even other arrays.

You can build arrays directly by assigning them to an array-definition, or indirectly by adding array-definitions to them. Assigning values to indexes that are not in the range [0, sizeof(array)-1] will lead to errors. Another way of building arrays is to allocate() them. In that case you will get an empty array of the allocated size, to which you can start to assign (within the size limits, that is).

Examples:

	int *arr, *arr2;

	arr = ({ 1, 2, 6, 10 });
	arr += ({ 11 });
	arr2 = allocate(3);
	arr2[0] = 4;

Arrays are stored by reference, so all assignments of whole arrays will just copy the address. The array will be deallocated when no variable points to it any longer.

When a variable points to an array, items can be accessed with indexing: 'arr[3]' as an example, will give the fourth element of the array 'arr'. Note that the numbering starts from 0. The 'sizeof()' function will return the size of the array. That means that there is no element numbered sizeof(arr) in an array, all elements have lower numbers.

The name of the array being indexed can be any expression, even a function call: 'func()[2]'. It can also be another array, if this array has pointers to arrays.

Now 'arr[1][2]' is a valid value, namely 12.

Elements of arrays can be treated just like normal variables, e.g.:

Then

would result in 'arr' being ({ "foo", "bar", "jacuzi" }).

14 - Special statements


There are some special statements that allow selection and repeating depending on testing conditions.

14.1 - if


	if (expr)
	    statement1;
	else
	    statement2;

The if-statement can be used to direct the flow of execution according to the value of 'expr'. If 'expr' is true, 'statement1' is chosen, if it was false 'statement2' will be executed. If you want more than one statement to be executed in either the if- or the else-part, you should enclose all statements in a block.

14.2 - for


	for (expr1; expr2; expr3)
	    statement;

The 'for' statement is used to create loops. The actual body of the loop is 'statement'. If you want more than one statements to be looped, you should enclose them in a block.

This is what happens: initially 'expr1' is once executed. Then, while 'expr2' yields true, 'statement' is executed. Every time 'statement' has been executed, or a 'continue' statement has been executed, execute 'expr3' before doing the next loop. The three arguments of 'for' may each be empty, in which case they are presumed to return true.

A 'break' in the 'statement' will terminate the loop. A 'continue' will continue the execution from the beginning of the loop, without executing the rest of the statement. Putting 'return' in the statement will leave the function, and therefore also the for-loop.

Example:

	for (i = 0; i < 10; i++)
	{
	    write(i + "\n");
	    foo += i;
	}

	for (;;i++)
	{
	    if (i == 15)
		break;		/* Stop at 13 */

	    if (i % 2 == 0)
		continue;	/* Don't print even numbers */

	    write(i + "\n");
	}

14.3 - while


	while (expr)
	    statement;

While 'expr' is true, execute statement. If 'statement' is more than one statement, it should be a block.

A 'break' in the 'statement' will terminate the loop. A 'continue' will continue the execution from the beginning of the loop, not executing the statements that are still left.

Example:

	while (i < 11)
	{
	    i += 3;
	    if (i % 2 == 0)
		continue;	/* Don't print the even numbers */

	    write(i + "\n");
	}

14.4 - switch


	switch (expr)
	{
	case val1;
	    statement1;

	...

	default;
	    statementd;
	}

The 'switch'-statement is like a multiple 'if'-statement. It considers a value, and picks the all cases that match to execute, starting from the top. The default case always matches, but you are not required to define it. If 'statement1' consists of multiple statements, you are not required to make a block of it.

Beware! If you only want to execute the statement of one of the cases, make sure you put "break;" before the next case. If you forget to do that, every matching case will be executed. Usually you will want to use "break;".

Example:

	switch (i)
	{
	case 1:
	    write("I was 1.\n");
	    break;
	case 2:
	case 3:
	    write("I was 2 or 3.\n");
	    break;
	default:
	    write("I was not 1, 2 or 3.\n");
	    break;
	}

The 'switch'-statement will work on strings as well.

15 - Comments


Because people tend to forget how a function exactly works, it might be usefull to put a little word of explanation in your code to freshen up the memory. This can be done by putting comments in your file. A comment is opened with the sequence '/*', and closed with the first encountered '*/'. Note that this rule provides you from putting comments inside comments.

In the standard mudlib standard headers are used to explain what all functions are about, perhaps it is a good idea to adopt such headers.

Here is an example of such a header:

	/*
	 * Function name:   foobar
	 * Description:	    This function computes how much rain would
	 *		    mainly fall in the plains in Spain.
	 * Arguments:	    arm: The number of armadillo's
	 *		    zuc: The zucchini-count
	 * Returns:	    0 on error; else the amount of rain.
	 */
	varargs static int
	foobar(int arm, int zuc)
	{
	    ....
	}

16 - The preprocessor


The preprocessor is really a kind of meta-compiler. Before the gamedriver tries to examine the code, the preprocessor skips through it, to see if there were any special commands for it, and it changes the code accordingly. After that the gamedriver will try to compile the code that remains. Like the normal compiler, the preprocessor ignores comments.

16.1 - #include


	#include "/path/file"
   or	#include <file>

The '#include'-statement will include the text of the specified file in the current file. The result is the same as what you would get if you typed the whole file on the same spot as the '#include'.

There are two ways to specify the file to include. Either the file is in quotes, like '"/sys/formulas.h"', in which case the preprocessor will use that as a path. Or the file is in brackets, like '<formulas.h>', in which case the preprocessor will start to search through the includedirectories that are known to it.

16.2 - #define, #undef


	#define ARMADILLO
	#define FOO	2 + 2
	#define SETNAME(pers,str,val) if (val * 3 > 5) \
				      pers->set_name(str);
	#undef ZUCCHINI

This statement allows you to define constants, or to set them to a value. The '#define'-statement causes all occurrences of the defined first part to be textually replaced with the second part. It is possible to define functions that use variables, where the arguments given are textually copied to the places where they are used in the function. The resulting text is copied to where the function-call was made.

If no value is assigned to the defined object, is it defined to true. Things that are not defined are false. To make sure that a certain constant is not defined, you can '#undef' it.

Beware! It is not for nothing that I stress that things are textually replaced. Look at the former defines. What would the next things give?

	write("FOO * 5 = " + FOO * 5 + "\n");
	SETNAME(obj, "Fatty", 1+1);

Don't be mislead by the interpretation of FOO. "2 + 2" Might mean "4", but when multiplied by 5 behaved differently! "2 + 2 * 5" is evaluated as 2 + (2 * 5) which is not the same as (2 + 2) * 5!

The same thing happens with SETNAME: 'val' is replaced with '1+1', which gives '1+1 * 3' and is evaluated as 1 + (1 * 3), and not as the intended (1 + 1) * 3!

It is safest to surround the define with braces, or to enclose the constant names in braces.

Note that all constantnames that are used for the preprocessor are in capitals. That is just a convention which makes it easier to keep normal variables and defined variables apart.

16.3 - #if, #ifdef, #ifndef


	#if DEBUG == 1
	...
	#else
	...
	#endif


	#ifdef ARMADILLO
	...
	#else
	...
	#endif


	#ifndef ZUCCHINI
	...
	#else
	...
	#endif

The '#if'-, '#ifdef'- and '#ifndef'-statements allow you to selectively include code. Everything that is in the right 'if'-branch is compiled, the rest not. Behind the '#if'-statement may be an expression with preprocessor-constants.

'#ifdef' checks to see if the constant behind it is defined or not, '#ifndef' checks if it is not defined.