LPC					Last update:   April 19th, 1993
---


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 indentation
	 7	...	The syntax
	 8	...	Types
	 9	...	Operators
	10	...	Statements
	11	...	Functions
	12	...	Inheritance
	13	...	Arrays
	14	...	Special statements
	14.1	...	    if
	14.2	...	    for
	14.3	...	    while
	14.4	...	    switch
	15	...	Comments
	16	...	The preprocessor
	16.1	...	    #include
	16.2	...	    #define, #undef
	16.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. Use the login
"lpmud:<your 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:

    - types,
    - operators to use on variables of those types, and
    - statements to work with the outcome of the former two.

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:

    - Move the whole function foo() to somewhere above line 42.

or  - put the prototype of the function foo() above line 42. A prototype
      of a function is the whole header, including possible types,
      followed by a semicolon ";". E.g.: "nomask void foo(int num);"

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:

	int *arr;

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.

	arr = ({ ({ 1, 2, 3 }), ({ 10, 11, 12, 13 }) });

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

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

	arr = ({ "foo", "bar", "zucchini" });

Then

	arr[2] = "jacuzi";

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.
