[Next]
[Up]
[Previous]
[Contents]
[Index]
Next: 4. Flow Control
Up: NannyMUD LPC
Previous: 2. Fundamentals
Subsections
3. Types, operators and expressions
3.1 Identifier Names
Valid
identifier names are formed from a combination of the characters a-z,
A-Z, underscore, and the numbers 0-9. They must start with a letter, or
underscore. Case is significant, 'x' and 'X' are two different variable names.
The
keywords of the LPC cannot, naturally, be used as variable names. A
list of such reserved keywords can be found in appendix
A.
3.2 Type Checking
Global variables that are explicitly initialised when declared are
typechecked at load-time. Apart from that, there is normally no global
typechecking done in LPC, neither at load-time, nor at run-time. There are,
however, two
#pragmas that can be declared to get global type-checking at
load-time:
#pragma strict_types and
#pragma save_types.
The first turns on typechecking at load-time, while the latter saves the
type information so it can be used by inheriting objects. Unless those
are used, the types given in the source code file is just a kind of
documentation.
Local
type-checking can be forced by declaring the types of functions;
this is covered in section 5.
3.3 Data Types and Modifiers
All variables are initialised to zero (0).
Variables, functions and expressions in LPC can have one of the types
int,
status,
float (but see below),
string,
object,
mapping, and
mixed.
There is one extra type available for functions,
void. That,
and type-checking rules for functions, are covered in section
5.
It should perhaps be emphasised that LPC does not, despite the existence
of the type 'float', use floating point numbers. The various data types
somewhat more in detail:
- int
This is a signed integer. Examples of integers are 0, 4711,
-5723, 0x7ffffff and 'c'.
- status
This should really be a boolean, and accept only the values 0 and 1,
but it is a full int.
- float
This is just another name for 'int', and a bad one at that. Don't
fool yourself, or others; do not use this.
- string
This is a string, and not (as in C) a pointer to a string.
- object
This is a pointer to an object. If the object is destructed,
the pointer will be zero.
- mapping
This is a mapping, the LPC word for an associative array.
More will be said about mappings in section 6.3.
- mixed
This is a way of telling the driver that you don't know what
type this variable will have. A variable of this type can have
any of the types above, and it can be explicitly casted to a
specific type.
It is also possible to have
arrays of variables of any type. Arrays and
mappings are treated in the section 6.
Several
modifiers can be applied to the variables:
static,
private,
public, and
nomask.
- static
A variable declared as static will not be saved nor restored using
the efuns1
save_object()2
and restore_object()3.
- private
This can be given for both functions and variables. Functions that
are private in an object, A, cannot be called through
call_other4
from any object, not even by the defining object itself. Also, they
are not accessible to any object that inherits A.
- public
A function defined as public will always be accessible from
other objects, even if private inheritance is used. See section
7.1 for details on inheritance.
- nomask
A symbol defined as nomask cannot be redefined by
inheritance. It can still be used and accessed as usual.
3.4 Constants
An integer
constant like 4711, is an integer. Likewise is -12, 'c' (with the value 99),
'\077', and 0xff (hexadecimal representation of 255). Integer
constants overflowing the range are set to the max range. The type status
behaves exactly the same way, since it is in really an integer.
String constants are a set of characters surrounded by double quotes, ".
Examples of string constants are "hello world!", "\077", and "\\ \" a\n".
3.5 Declarations
All
variables must be
declared before they are used. A declaration contains
a type, followed by a comma-separated list of variable names. For example,
int i, j, k, l;
int a;
declares the variables a, i, j, k, and l, as being integers.
Global variables (i.e. the variables
declared outside any function) can be declared in any place, as long as the
declaration precedes their first use. Global variables can also be
initialised when declared (and are then type-checked):
string tmp_s = "foo faa fuu";
declares a string variable tmp_s, and initialises it to have the value
"foo faa fuu".
3.6 Arithmetic Operators
There are several arithmetic operators available in LPC: +, -, *, / and %.
- The + operator
Unary +, as in +12, is allowed (on integers) but has no effect. The
binary use, expression1 + expression2, works on integers,
strings, arrays and mappings, as well on the combination
string/integer.
For integers, the result is the usual
arithmetic sum. For strings, the result is the concatenation of the
two strings. Adding two arrays, the result is an array containing the
elements from both arrays. Addition of mappings work similarly to
the addition of arrays. Addition of an integer and string converts the
integer to a string and concatenates the two strings.
- The - operator
Used as an unary operator, -expr, the operator changes
the sign on the value of the expression. In this case, it works only
on integers. Used as a binary operator,
expr1 - expr25
it works on integers and arrays.
For integers the result is the common arithmetic difference, and for
arrays it is the elements of 'expr1', with all elements of
'expr2' excluded.
- The * operator
The * operator has only a binary use: expr1 * expr2.
It works on integer values only, and then gives the usual arithmetic
product.
- The / operator
The / operator has only a binary use: expr1 / expr2.
It works on integer values only, and then gives the integer division.
- The % operator
The % operator has only a binary use: expr1 % expr2.
It works on integer values only, and then gives the remainder of the
integer division.
3.7 Relational and Logical Operators
The relational operators are >, >=, <=, <, == and != .
- == and !=
Those operators work when comparing all datatypes. For integers and
strings, equality means that they have the same value. For objects,
equality occurs when two expressions yield a pointer to the same
object. For mappings and arrays, equality occurs when two expressions
yields the same memory (see also sections 6.2 and
6.3).
- <, <=, >= and >
The operators < and > works on both integers and strings.
Integers are compared as usual, while strings are compared using the
ASCII collational sequence. For example, "a" is less than "b", and so
is "aa".
- ||
This is the logical 'or' operation. The result of
expr1 || expr2 is true if 'expr1' is true
(in which case 'expr2' is not evaluated), or if
'expr1' is false and 'expr2' is true.
Remember that anything that isn't zero is considered true. Thus,
an existing object, an empty array or mapping, and an empty string
are all considered true.
- &&
This is the logical 'and' operation. The combinded expression
expr1 && expr2 is true if both expressions are
true. 'expr2' is not evaluated if 'expr1' is false.
- !
This is the logical 'not' operator. It turns any true value into zero,
and zero into one.
- ~
The operator ~ is the boolean not operator (ones complement), an
unary operator. It works solely on integers.
3.8 Increment and Decrement Operators
The increment, ++, and decrement, -, operators works on variables with type
integer.
- ++var
This increments the value of variable 'var', and returns the
new value to the expression.
- var++
This increments the value of variable 'var', and returns the
old value to the expression.
- --var
This decrements the value of variable 'var', and returns the
new value to the expression.
- var--
This decrements the value of variable 'var', and returns the
old value to the expression.
3.9 Bitwise Operators
There bitwise operators are |, ^ , &, << and
>>.
- |
This is the bitwise 'or' operation. It works on integers only.
- ^
This is the bitwise 'xor' operation. It works on integers only.
- &
This is the bitwise and operation. It works on integers, and has been
extended to work on arrays. In the latter case, the result is an array
that holds the elements that occur in both arrays, i.e. the
intersection.
- <<
This is the left shift operator; expr1 << expr2
shifts 'expr1' left 'expr2' bits.
- >>
This is the rights shift operator; expr1 >> expr2
shifts 'expr1' right 'expr2' bits.
3.10 Assignment Operators and Expressions
The
assignment of a value to a
variable is usually done through the
construction var = expr. The value of 'expr' is assigned
to the variable 'var'. The value of the whole is the new value of the
variable 'var'.
The expression
i = i + 4;
can be written in a shorter form:
i += 4;
where the operator += is known as an assignment operator.
There are several assignments operators available in LPC:
+=,
-=,
*=,
/=,
%=,
&=,
^ =,
|=,
<<=
and
>>=.
If 'expr1' and 'expr2' are expressions, and 'op' one of the above operators,
expr1 op= expr2;
is equivalent to
expr1 = (expr1) op (expr2);
but 'expr1' is evaluated only once.
3.11 Other Operators
There are a few operators available that do not fit into any of the above
categories.
- ,
The comma operator, expr1 , expr2, computes the value of
'expr1', throws away that value and then computes 'expr2', which
value is the value of the total expression.
- []
The index operator, [], works on arrays, mapping and strings.
For the use of index on strings, arrays and mappings, see the
discussion in the section
6.
Index starts at zero, always. Negative index counts from the end
of the string/array. You can use ranges as index.
- ..
This is the range operator, and it can only be used within an index
operator. It can be used to pick out a certain range from an array,
or a sub-string from a string. Both the start and the stop position
must be given, both must be zero or positive, and stop must be
larger than or equal to the start position.
- -> This operator is called 'call-other'. It's usage is
expr1 -> name(...). 'expr1' can either be an object, or a
string. In the latter case, an object is created by loading the file
named by 'expr1'. Then the function 'name' in that object is called.
It is then a 'call function in another object', or 'call-other' for
short. It does exactly the same work as the efun6
'call_other()'7
- (type)
If an expression is of the type mixed, it might be possible to convert
it to another type using the cast operator, (type)expr.
This only work if the type of the expression fits into the type of the
cast.
- ::
This operator is used to call functions in an inherited object. It can
be used in two forms: ::function() and
filename::function(). The latter specify exactly in what
inherited object the function is called, which can be of interest in a
situation where several objects are inherited.
3.12 Conditional Expressions
The statements
if (expr1)
expr2;
else
expr3;
can be written as an expression using the
ternary operator :? :
expr1 ? expr2 : expr3. This is truly an expression and can thus
be used wherever an expression can be used. It makes for stream-lined
code, but can also reduce the readability of your code drastically.
3.13 Precedence and Order of Evaluation
Here is the list of operators in LPC, listed in descending precedence, from
left to right and top to bottom (i.e. :: has higher precedence than ->,
and [] has higher precedence than -).
:: |
-> |
[] |
-- (post-decrement) |
++ (post-increment) |
~ |
! |
- (unary minus) |
-- (pre-decrement) |
++ (pre-increment) |
(type) (type cast) |
/ |
% |
* |
- |
+ |
>> |
<< |
<= |
< |
>= |
> |
!= |
== |
& |
^ |
| |
&& |
|| |
?: |
/= |
%= |
*= |
>>= |
<<= |
^ = |
|= |
&= |
-= |
+= |
= |
, |
.. |
|
|
|
|
|
The
order of evaluation of expressions are basically from left to right.
The precedence of operators can of course change this. Unlike many other
programming languages, LPC has the property that in the case of two
expressions having the same precedence, they are evaluated left to right.
Thus, for example, there is no ambiguity of what the value is of the
subscript in
a[i] = i++;
as it will always be the old value.
3.14 The time cost of everything
The MUD process is single-threaded. In order to prevent any piece of
LPC-code to be able to hang the MUD, every operation performed when
interpreting the code has been associated with a cost, called
'nodes'.
The sum of evaluated nodes is not allowed to pass a certain limit,
determined when the driver is compiled, known as 'max eval cost'. It is
good to know the limit is there; the exact value is not that important.
Basically, the association of the cost with the operation is arbitrary
in that it is not based on real life time; rather it is based on the
driver hackers (sourcerers) whims.
Footnotes
- ... efuns1
- See section 5.1
- ..._object()2
- 'save_object()' is a function provided by
the driver to dump a representation of an objects variable and
their values to file.
- ..._object()3
- 'restore_object()' reads an objects
variables and values from file.
- ...call_other4
- 'call_other' is a function provided by
the driver to call functions in a specified object. The specified
object can, naturally, be the calling object itself. That might
seem a waste, but consider 'shadows' (see 8.1).
- ... expr25
- Strictly speaking, it is the value
of expression1, etc.,
that is used. Pointing this out every time makes the text
rather cumbersome, so we adopt the slightly sloppy habit
of letting this be understood by the context.
- ... efun6
- See
section 5.1.
- ..._other()'7
- In fact, it is exactly the same thing,
written in two different ways.
[Next]
[Up]
[Previous]
[Contents]
[Index]
Next: 4. Flow Control
Up: NannyMUD LPC
Previous: 2. Fundamentals
Mats Henrik Carlberg
1998-03-25