Introduction to LPC4
Written by Fredrik Hubinette
LPC is a dynamic, MUD-oriented language with a syntax that looks like C. The
mode of programming is similar to structured BASIC or byte-compiled LISP in
that all data is dynamically allocated, and that a variable can hold any value
not just what it was declared as. It is not a compiling language, merely
byte-compileing and the code is then interpreted by a program written in C.
The compiler and interpreter are integrated in a program called driver, it is
capable of dealing having any number of programs in memory, but it is not
multitasking, thus only one program can run at a time.
A program is a set of functions and variable declaration read from a
LPC-source file, there is no intermediate form like 'executables' or dot-o
files. From these programs, objects are cloned. Every object has it's own
memory for the global variables in the program. It is not possible to call
a function without an object to call it in.
All values in LPC consists of a cell contaning both the type of the cell, and
the data itself. The data might be an integer, float or a pointer to some
other data. In C only the compiler knows the type of a variable, it is not
possible to know if a variable is an integer or float by looking at it's
value afterwards.
Now let's look at an example program:
string gazonk;
void main(int argc,string *argv)
{
int e,foo,bar;
for(e=0;e<10;e++)
{
write(e);
}
}
Ok, everything looks like C, except that write() statement, which is a
predefined LPC function. Documentation of all such function can be found
in the directory doc/efun. If you aren't familiar with C, then maybe some
explainations might be in order:
The first line 'string gazonk;' is a declaration of a global variable of the
type 'string'. Then follows a function that is declared to return void, which
means that it don't return anything and takes an int called argc and an array
of strings called argv as argument. Then the line 'int e,foo,bar;' reserves
space for 3 local varables of the type int. Then follows a loop that first
sets e to 0, and runs while e<10 and writes e and increases e for every loop.
See doc/lpc/control to find out more about loops etc.
Let's assume this program was saved in the file test.c and that we want
to load and we want to make a program that loads it and calls main().
Then this is what you would write in another program:
void do_it()
{
object o;
o=clone_object("test");
o->main(1,({"foo"}));
}
This example is somewhat simplifyed, normally a full path name is needed as an
arument to clone_object. Note that o will be a pointer to the object cloned,
not the object itself. The operator -> returns a function pointer to the
function main in the object pointed to by o. The paratheses are then the
operator that calls the function that is written just before it. Here I give
main() the arguments 1 and an array with the string as the only element.
Because of LPC's dynamic structure, the array is not really a constant,
instead a structure in memory is built at execution time and a pointer to it
is sent to main(). It is then freed when it no longer needed.
All lpc types have the properties of ether a value or a pointer, ints, strings
and floats all works like values, while arrays, mappings, lists, objectpointer
and functionpointers all behave like pointers. The main difference is that
values are copied when an assignment is made, while in the case of a pointer
anoter reference is made to the object pointed to. Thus if a is an array and
b is assigned to a like: b=a; then any destructive change in the array a will
also be seen in the array b because they will be pointers to the same array.
A chain of calls always starts from with a call from inside the driver, all
functions that has special meanings to the driver are described in the
directory doc/lfun.
Let's look a little closer at the LPC syntax:
[] will mean that that part is optional unless otherwise is explained.
<> will mean that that part should be filled in by some text unless otherwise is explained.
... means that the previous list can be of any number of items, even zero.
- A global variable definition
<type> <name> [=<statement>] , <name> [=<statement>] , .... ;
This defines global variables and optionally sets their values. If
they are not initiated they will be set to an integer zero.
- A function declaration looks like this
<type> <name> ( <type> <arg_name>, <type> <arg_name> , ... ) ;
The fist type is the return type, then follows the name of the funcion.
The second type is the type of the first argument, which name follows.
Function declarations are used to let the compiler make an function
header that can be used before the function is defined. This is called
a 'forward declaration' in PASCAL.
- A function definition
<type> <name> ( <type> <arg_name>, <type> <arg_name> , ... )
<block>
The first line corresponds to a function declaration, that is
exacly what it is, then follows the 'body' of the function, which is a
program block with the argument for the funcion defined as local
variables.
- A block
{ <local declarations> <statements> }
The local declarations is just a list of variable declarations of the
same form as the global declarations but the variables are only usable
within the block.
- A statement
- A statement can be many things, it can be a for-loop, a function-call
an if-else statement, a switch and assignment or simply a block.
Basically can be said that a statement has two forms: one containing
another block, like if-else statements, switches, for-loops etc. and
one that is an expression followed by a semicolon.
- Expressions
- Here is a list of operators that can be used in expressions, listed
in ascending priority order:
<> and [] are literal in this listing.
A,B,C... are any expression
a,b,c... are variables
- Priority 0:
- A?B:C returns B if a is nonzero and C otherwise
- Priority 1
- a=B assigns the value of b to the variable a and returns that value
a+=B same as a=a+(B)
a-=B same as a=a-(B)
a/=B same as a=a/(B)
a*=B same as a=a*(B)
a&=B same as a=a&(B)
a|=B same as a=a|(B)
a^=B same as a=a^(B)
a&&=B same as a=a&&(B)
a>>=B same as a=a<<(B)
a<<=B same as a=a>>(B)
- Priority 2
- A||B returns A if A is nonzero otherwise B
A&&B returns A if A is zero otherwise B
- Priority 3
- A|B returns A or B bitwise ored
A&B returns A and B bitwise anded
A^B returns A xor B
- Priority 4
- A==B returns 1 if A is equal to B otherwise 0
A!=B returns 1 if A isn't equal to B otherwise 0
A<B return 1 if A is lesser than B otherwise 0
A>B return 1 if A is greater than B otherwise 0
A<=B return 1 if A is lesser than or equal to B otherwise 0
A>=B return 1 if A is greater than or equal to B otherwise 0
- Priority 5
- A<<B returns A bitwise shifter B positions right
A>>B returns A bitwise shifter B positions left
- Priority 6
- A+B returns A plus B
A-B returns A minus B
- Priority 7
- A*B returns A multiplyed with B
A/B returns A divided by B
- Priority 8
- !A returns 1 if A is zero, 0 otherwise
~A returns the bitwise compliment to A
(type)A returns a casted to the type inside the parentheses
a++ increases the variable a (which must be an integer) with 1 and
returns the value a had _before_ it was increased
++a increases the variable a with 1 and returns the value of a
a-- decreases the variable a (which must be an integer) with 1 and
returns the value a had _before_ it was decreased
--a increases the variable a with 1 and returns the value of a
A[B] return the index B in the array or mapping A
(this is also a variable)
- Priority 9
- A[B..C] return all elements in A from B to C
A(B) calls the function A with the argument b
A->foo returns the function called foo in the object A
({A,B}) returns an array with A and B as elements
([A:B]) returns A mapping with A as an index to the data B
(<A,B>) returns A list with A and B as memebers
1 2 3 4 ... returns an integer
"foobar" returns the string foobar
1.0 2.2 3.14 ... returns a float
foobar(A,B) or efun::foobar(A,B) call the efun called foobar with A
and B as arguments.
(A) returns A
Note that the order of evaluation within prioritys normally is left to
right, but not in all cases.
See Operators for further details.
- Inheritance
- A program may 'inherit' one or more programs, this is simply a copy operation
that copies all the functions and variable defenitions to the new program, it
does however have a lot of advantages over the include-statement, which
merely includes text into the new program: It saves memory, because the body
of the functions is never copied, the original is used instead. It saves
time, because the inherited code doesn't have to be compiled again. To
inherit a program simply write 'inherit <filename>' in the beginning of the
file. Note that defines and macros will not be inherited, though.
LPC4 vs LPC3 - For thoose of you who know some LPC3
A lot of changes have been made in LPC4 compared to earlear versions
I will try to describe what I have done, and why here.
- Floats
- Obviously floats can be very nice, so I added them as a new type.
- Lists
- A list is really a somewhat ill-named type. It is _not_ a type for
linked list. It is instead a type of array in which the order of the
members is not important. Like a mapping without data. It has been
made so that lookups, and logical operations such as anding and oring
is quite fast.
- Functionpointers
- Functionpointers is now used for _all_ LPC function calls (not for
efuns though) The name of a defined function is a 'constant
functionpointer and parentheses are used to call it. Other ways of
writing functionpointers include: file::fun, ::fun, obj->fun and
lambdafunctions. A funcitonpointer consists of an objectpointer
and an index that tells which function is pointed to in that object.
- Lambdafunctions
- Is essentially a nice way of defining functions inside another
function for instance:
foo()
{
return lambda() { return 2; }
}
Will return a functionpointer to a function that will return 2 when
called.
- No 'master' objects, only clones
- I never liked the need for a 'master' for the clones so I removed
the cloning of a masterobject and made the functions load() and
update() to handle programs. I also invented a way to 'label' clones
so that it would still be possible to address objects not yet loaded.
- Object labels:
- When a string is casted to an objectpointer, this will happen:
If a # is present in the string, the part before it will be called
file and the part after a label. If there is no # in the string or
the label is an empty string, then only the file will be considered.
It the program residing in the file file isn't already loaded, it
will be loaded and a clone will be made. This clone will be labeled
with the original string (without the # if the label was empty) and
the label will be sent to create() as an argument. The object may then
selfdestruct if it didn't like it's label, otherwise that object will
be returned and hashed with that label. Next time that string is
casted to an object, this clone will simply be digged out of the
hashtable.
There is a similar function in clone_object(), with the only
difference that it doesn't actually label the object, it just sends
the label to create()
- A lot of new efuns have been added (of course)
- read about them in doc/efun
- The compiler has been rewritten
- It now uses parse-trees and optimizes the code somewhat.
- Casting
- Casting has been changed, casting to most types now actually does
something. A list of actions during casting follows:
- string to int: atoi()
- string to float: atod()
- string to object: loads an object as described above
- string to function: is equal to this_object()->string
- No casting
- Previously some implicit casting were done from strings to objects
at runtime, now the compiler tries to detect where such casting is
needed and insers castings there. However, if the types doesn't match
exactly (ie. if one of them is 'mixed') no casting implicit casting
will be done automatically.
- Communication
- The old communication code that used interactive objects has been
replaced by general socket efuns. The removal of the interactive
structure also led to the removal of this_interactive(), find_player()
ed(), query_idle(), snoop(), trace(), traceprefix() and some other gunk
in the driver.
- Shadows
- has been removed, they only complicated things.
- Binary strings
- Some functions can now handle binary strings, that is strings that
contains zeros.