Copyright 1996, 2003 Ronny Wikh and Noah Gibbs
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 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.
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:
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.
+. This makes strings much easier
to use.
([
"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.
* 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.
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!
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.
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);
}
We need to define what a statement and what an expression are in order to proceed.
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.
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().
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();
}
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.