Phantasmal MUD Lib for DGD

Phantasmal Site > DGD > Untitled document (index.base.html) > (untitled)

3.2 Basic LPC

LPC is similar to the language C, although some differences exist. Experienced coders will find it is basically a simplified C with some new convenient types and new functions. Some inconsistancies exist but, as with C, they're not a problem if you're aware of them.

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.

Data Types

An object holds information in variables. Variables are a sort of virtual container that holds information. It's called a variable because the information is allowed to change later. Most objects process information with functions. Functions can use and return data of various kinds.

In principle only one kind of data type is needed, a sort of general container that would hold anything you wanted it to. LPC calls this type 'mixed'. Usually, though, it's much more useful if you can distinguish between different types of information. Knowing what's in a variable can be a very good thing. It greatly improves on the time it takes to write and debug an object.

In LPC it is possible to use only data of type 'mixed'. In the first versions of the language, that was the only data type available. With modern LPC, however, it's better to avoid mixed variables when you can.

LPC lets you declare variables of these types:

void
Void is only used as the return type of functions that don't return any data at all. You'll never declare a variable of type void, though you'll use it like other types.
int
Integers (LPC calls them 'int' for short) are whole numbers, normally between -2147483648 and 2147483647. For instance 3, 17, -32, and 999 are all perfectly good integers. You can write integers in a couple of different ways, such as in hexadecimal (base 16), but they're still the same data type underneath, they're just written differently.
float

Fractional numbers, usually approximately between 1.17549435e-38 and 3.40282347e+38. For instance 1.3, -348.4, 2.0 and 4.53e+4 are floating-point numbers (LPC calls them 'float'). In case you're not familiar with Scientific Notation for numbers, 4.53e+4 is the same as 4.53 x 10^4, 4.53 * 10000, or 45300.

LPC doesn't recognized numbers like 1. or .4711 as floats. You have to specify both an integer and a decimal part for every number, even if they're zero for the number you're typing. LPC's just funny that way.

string
Strings are simply a series of printable characters. They're written with the characters in double quotes. For instance "x", "the string", "Another long string with the number 5 in it". Strings can contain special characters like newline (written as "\n") to end a line. A lot of LPC operators can handle strings directly, unlike in C. For instance, you can put two of them together with +. This makes strings much easier to use.
mapping
Mappings are another handy LPC feature. A mapping is simply a list of associated values. Assume you want to remember the ages of people. Say that Olle is 23, Peter is 54 and Anna is 15. In LPC you can turn this into the mapping ([ "Olle":23, "Peter":54, "Anna":15 ]). The value to the right has been associated to the key to the left. You can then find the value by looking for the key. DGD lets you do intersections and unions and other operations on mappings.
object
Object pointers are references to LPC programs that have been loaded into memory. The object pointer may point to an original object, or to a clone.
Array
All of the above can appear in arrays, indicated by a * in front of the variable name in the declaration. Arrays in LPC are more like lists than arrays in C. LPC and DGD let you do unions and intersections and other array operations that most languages don't.
mixed
Type mixed is a general descriptor covering all the others, a sort of jack-of-all-trades type. An object of any other type may be stored within a mixed type. But when a regular type can be used, it should. Don't use mixed just because you don't want to remember the type's name. The type system is there to catch errors for you. Let it.

There's also a special value called nil, which is not really any of the types above. A string, mapping, array or mixed variable that isn't initialized has a value of nil. A freshly-allocated array is often allocated to nil. You can do some tricks like assigning nil to an entry in a mapping to remove it. Nil is like an undefined value, or a zero for things that aren't necessarily numbers. You'll see more of it in examples later on.

If you need to know the limits of integers, characters or floating-point numbers, you can check DGD's include/limits.h and include/float.h files. They list limits of the various data types. Bear in mind that DGD can easily be compiled with different integer and floating point limits, so it's good to make your code check the sizes. They may be different next time your program runs!

Variable declarations

A variable is a string of letters identifying an information container, a place to store data. The container is given a name consisting of 32 characters or less, starting with a letter. No special character other than the '_' used to separate words is ever used. Variables should always be given names that reflect how they are used. You declare variables like this:

<data type> <variable name>, <another variable>, ..., <last variable>;
e.g.
    int        counter;
    float      height, weight;
    mapping    age_map;
    

Variables must be declared at the beginning of a block, immediately after the first { and before any other statements. Global variables, variables that are available in all functions througout the program, should be declared at the top of the file.

Variables are initially set to 0 or to nil, and not necessarily to the obvious 'empty' values. Mappings, arrays and strings will all be set to nil and not to ([]), ({}) or "" as you might expect.

Arrays and mappings should be initalized to their empty values (({}) and ([]) respectively) before being used. You can't add a value to a mapping that's set to nil, for instance, and you'll get a runtime error if you try.

Function declarations

A function's declaration must state what kind of data type it returns, if any. A function name is a label much like a variable name, consisting of 32 characters or less and starting with a letter. No special characters other than _ should be used to separate words. Use function names that clearly reflect on what they do. A function declaration looks like this:

/*
 * Function name: <Function name>
 * Description:   <What it does >
 * Arguments:     
 * Returns:       <What the function returns>
 */
<return type>
<function name>(<argument list>)
{
   <code expressions>
}
    
The beginning comment, while optional, is highly recommended. Even if you don't use precisely this form of comment, you should state what the function does and what it expects to be given as input. Here is an example function:
/*
 * Function name: compute_diam
 * Description:   Compute the diameter of a circle given the 
 *                circumference.
 * Variables:     circumference - the circle's circumference
 *                pi - a famous irrational constant
 * Returns:       The circle's diameter
 */
float compute_diam(float circumference, float pi)
{
    float rval;

    /* Circumference = pi * diameter, so
       diameter = circumference / pi */

    rval = circumference / pi;

    return rval;
}

The argument list is a comma-separated list of data types. It specifies what kinds of data will be sent to the function and assigns names to this data for later use. The data recieved will only be usable inside the function, unless you explicitly send it elsewhere. The function's argument names are valid only within that function itself.

(In order to save space and improve on legibility in the text, I won't put a header on every example function).

A function that doesn't return anything should be declared as void. For instance:

void write_stuff(string mess)
{
    write_file("/usr/Bob/myfile.txt", mess);
}
    

Statements and Expressions

We need to define what a statement and what an expression are in order to proceed.

Statements

A statement is sort of a full sentence of instructions, made up from one or more expressions. Statements usually cover no more than a single line of code. Sometimes it's necessary to break it up though if it becomes too long simply to improve on legibility -- a little like avoiding long run-on sentences. For most statements you simply break the line between two words, but if you are in the middle of a string you need to add a backslash (\) at the end of the line in order for the gamedriver to understand what's going on.

DRIVER->message("This is an example of \
a broken string.\n");
    

However, breaking a statement with backslash is extremely ugly and makes the code hard to read. It's usually possible to break the line naturally at the end of a string, or between two operators of some kind, or even just split the string in half and add the two parts together with the + operator. The only time the backslash really is necessary is in #define-statements, which we'll mention later.

DRIVER->message("This is a better way of " +
                "breaking a string.\n");

Statements in LPC almost always end with ;. It's considered good practice to put a newline there as well. That is to say, you shouldn't put multiple statements on the same line if you can help it. Keeping them separate makes them easier to read.

Expressions

An expression is an instruction or set of instructions that results in a value. A variable is an expression since it yields its contents as a result. a + b is a valid expression, because a and b are variables (expressions) and + is an operator that takes two expressions to make another expression. a = b + c; is a full statement ending in a ;. Because the = operator returns a value, a = b + c is an expression, but when you add a ;, it becomes a statement. It's like when you add a period to the end of a bunch of words and suddenly you have a sentence.

Function calls are valid expressions. They are written simply as the name followed by a set of parentheses with the arguments that the functions uses listed inside. Take the simple function max() for example, that returns the maximum of the two floating-point arguments. To determine the maximum of 4.0 and 10.0, you would write max(4.0, 10.0) as the expression. The result of the function call must be stored or used in an expression, or it is lost. That's fine if you're calling the function because of some other effect it has, such as write_file().

The block statement

There are a lot of statements such as conditional statements that in certain circumstances execute only one specified statement and no more. Suppose you want to have several statements executed and not just one? Well, there's a special statement called block statement that will allow you to do that. A block is defined as starting with a { and ending with a }. Within that block you may have as many statements of any kind (including variable definitions) as you like. The block statement shouldn't end with a ;, though usually it doesn't matter if you accidentally put one there. Example:

if(my_var < 3)
{
  statement_one();
  statement_two();
  statement_three();
}
    

The ';' statement

As stated ; is mostly used to terminate statements. However it's also a statement in its own right.

The ; on it's own will simply be a null statement causing nothing to happen. This is useful when you have test-clauses and loops (described later) that perform their intended purpose within the test or loop clause and aren't actually intended to do anything else. Just remember that anywhere you're allowed to have a statement, you can just put a ; as a statement that does nothing.