@@SECTION Tutorial A Quick LPC Tutorial
LPC is a powerful, extensive and subtle language. But it's pretty easy to learn the basics, at least enough to get by. This tutorial will show you the basics without getting bogged down in the details. There's plenty of time for that later on.
@@SECTION Tutorial.GettingStarted Getting StartedNow that you have the Kernel Library running, it's time to start learning some simple LPC code. There are two ways to test your LPC code with the Kernel Library. We'll start with the simpler way, which is called the "code" command.
Log in as admin, if you haven't already. Type code 7+4. You should see a response like $0 = 11. The $0 is a value that DGD will store for you. You can refer to it later whenever you like. For instance, if you typed code $0+9, you'd see a result like $1 = 20. Note that this time the history value is $1, not $0. $0 keeps its old value, and a new history item, $1, gets the new value. The history number will keep increasing as you perform commands that place values into the history list.
Next try typing code status()[ST_VERSION]. Note the opening and closing parentheses go before ST_VERSION in square brackets. If you do it right, you'll get the current version of DGD, such as $2 = "DGD 1.2.101".
The parentheses after a name mean to call a function by that name. Anything between the parentheses are the arguments of the function, also called parameters. For instance, try typing code sin(2.0 * 3.14159). You should get a result like $3 = -5.3071519e-6. Whatever result you get, it should be a very small number, like the above. That's because you're calculating the sine of two pi, which (if calculated absolutely perfectly) is zero. Now try typing the same thing, but with 2 instead of 2.0. If you're used to programming in C, the result may surprise you!
It turns out that ST_VERSION, in the example with status(), is actually a number. The code command defines certain constant values for you, and assigns names to them. So if you type simply code ST_VERSION, you should see the numerical value of ST_VERSION, 0, printed out. That's because underneath, ST_VERSION is just the number 0. We've assigned a value to it using the C Preprocessor, a very powerful part of the language that will be described in a later chapter.
You can put in extra spaces almost anywhere — between operators, before and after operators, between the function name and the opening parenthesis. Experiment liberally with where you can add spaces, it's useful to know. Note that you cannot add spaces before the code command. The Kernel Library won't parse it correctly, and will tell you that there isn't any such command as nothing. It's a bug in the Kernel Library's command parser.
The code command is very powerful, but very limited. It's very powerful because you can use it to evaluate any valid LPC code. It's very limited because you have to put all the code on a single line, and because it's hard to save old code commands to use again. This tutorial will use it to teach you the basics of LPC. Later, you'll find out how to put LPC code into files and compile those files into programs and objects.
@@SECTION Tutorial.VarsAndExprs Variables and Arithmetic ExpressionsThe code command gives you constants like ST_VERSION. It also gives you several other things, including a set of one-letter variables which it defines for you.
A variable is a named piece of storage space. A variable can hold a value, such as the integer 94 or the character string "Bob's your uncle!" (an integer is a whole number, either positive or negative, and a character string is a series of letters, numbers and punctuation like a word or a sentence). The variable has a name that you use to refer to that storage. The value of the variable may change over time. For instance, you may add 2 to the variable, giving 96 instead of 94. But the variable name will not change.
For instance, type code a = 94, a = a + 7, a. Make sure to type it as written, with the commas. You should see a result like $7 = 101. The commas are a special operator that means "do the things in order, but only worry about the value of the last one". So we did three things with that code statement. We set the variable a to have the value 94. Then we changed what a was equal to by setting it to its old value plus 7. Then, the very last part of the command, after the last comma, was a. Since comma means " only pay attention to the last one ", the value returned was a's new value of 94 + 7, or 101.
You can also separate statements with the semicolon. Due to strangeness in the way the code command works, if you use the semicolon operator with the code command at all, you must finish your code with a semicolon or an end-curly-brace. Otherwise you'll get very strange behavior indeed. Try typing code a = 94; a = a + 7; return a;. You should get 101, the same result as above. If you just get 94, you forgot the ending semicolon. Try it again. Note the 'return' statement at the end. When you don't use a semicolon at the end of your code command, the Kernel Library assumes you're just writing a little expression and you want its value. That's what you've been doing so far. If you end your code command with a semicolon or an end-curly-brace then you'll need to include your own return statement if you want anything returned. The return statement does what the last statement after the comma did in the last paragraph. Its value is what gets returned, and so that value is what gets assigned to a history variable like $9 or $26.
A variable in LPC isn't exactly like a mathematical variable. The equals sign above doesn't really mean equality in a mathematical sense. Instead, the equals is often called assignment. It is setting the variable to have a value. But unlike in mathematics, the variable can be repeatedly assigned, changing its value throughout your code. A variable's value is the most recent value that has been assigned to it.
You've already seen the + operator. There's also a minus, - which works the way you'd expect — it can be used to make a number or variable negative, or to subtract two numbers. The asterisk * means multiplication, and the forward-slash, / represents division. Remember that you can only add, multiply, subtract or divide integers with integers and floating point numbers (DGD speak for decimal numbers) with other floating point numbers. If you try to do otherwise, such as by typing code 7 + 4.1, you'll get an error. Try it and see.
Most of DGD's operations give exactly the answer you'd expect, but division of integers sometimes doesn't. If you divide, say, five by two, you'll find that DGD automatically rounds down to 2. DGD will always round toward zero when you divide integers. Test with the code command and see.
Integers are signed, and limited in size. If you try typing code 10000 * 10000 * 10000, you'll see a strange result — it's negative! That's because integers have a maximum value of a little more than two billion. If your numbers get larger than that, they'll wrap back around. Integer variables will wrap to negative numbers once they hit about two billion. If you're subtracting or multiplying and your integer variable gets smaller than the smallest allowed number (around negative two billion) it will wrap back into being positive. This wrapping is because DGD's basic numbers are of a fixed size, and there are only so many possible numbers that a single variable can represent. If you need larger or smaller integers than that, you'll find that DGD's Arbitrary-Precision Signed Numbers (called ASNs) are the way to go. They're explained in a later chapter.
The code command gives you a set of variables with names like a, b, c, et cetera. You can also declare your own new ones. Integer variables are declared to be of type int. So you might type code int sam; sam = 7; sam = sam + 4; return sam;. Note that you must declare the variables at the very beginning. If you try typing code a = 4; int sam; sam = 7;, you'll get an error. Variables can only be declared at the beginning of a block scope. What that means to you right now is that if you use variables in your code statements, put them right up front.
A note for users of the C language: in C, you could say
int sam = 7; instead of int sam; sam = 7;
and it would work fine. DGD doesn't allow you to declare and
initialize a variable in that way, so don't.
Not every value or variable is an integer, so not every variable
is declared using int. Also, unlike in C, there is no
such thing as an unsigned integer in DGD. This will give you a big
fat syntax error if you try.
Remember that decimal numbers or fractional numbers are called
floating point numbers. A floating point variable called
temperature would be declared as float
temperature;. Remember that you can only add, subtract,
multiply and divide floating point numbers with other floating
point numbers, not with integers. That means that to double a
floating point number, you'd need to multiply it by 2.0 rather than
just 2.
The LPC language is well-suited to math-intensive tasks, as you'd guess from the previous section. For instance, let's say you wanted to find out how many times you'd have to double the number two before you got to some very high number. That's something you could probably do in your head or on paper for small numbers. But for a number like ten million, you're probably better off letting the computer do it.
Try typing code int times; int count; times = 0; count = 2; while(count < 10000000) { count = count * 2; times = times + 1; } return times; Note that you must not hit return until the very end — you have to type the whole thing on one line. If you do it right, you should get 23 as the answer.
But is 23 the right answer? Hard to tell. So we should start with 2 and double it 23 times, just to be sure. Go ahead and type code int times; int count; times = 0; count = 2; while(times < 20) { count = count * 2; times = times + 1; } return count;. You should get the value 16777216 returned. And yes, that's more than ten million. So our code works! Now, how does it work?
You know about declaring variables and setting values, but while is a new one. While takes a condition in parentheses, and then a bunch of stuff between curly braces. The stuff in curly braces is done over and over again until the condition in parentheses is false. So in the first example above, the code will keep doubling the count variable and adding one to the times variable until count is no longer less than ten million. Then it will return count, which is how many times it had to double.
Once you understand the first example, you should be able to figure out the second example on your own.
There's another kind of loop called a for loop, which
is very similar. The first example, written as a for loop, looks
like this: code int times; int count; for(times = 0, count =
2; count < 10000000; count = count * 2, times = times + 1) { }
return times; A for loop is a lot like a while loop, but the
syntax is funny. Note that after the word "for", you have three
different pieces of code in the parentheses, each separated by a
semicolon. The first piece of the code is done before the loop
begins. In this case, it sets times to 0 and
count to 2. The second piece of code, count <
10000000 is checked every time through the loop. In this
case it's checking whether count is more than ten million, just
like the while loop did.
And the last section is done at the end of every time through the loop. So the things that were inside the while loop have moved to the for loop's third section of code. Putting code in the third section of the parentheses has the same effect as putting it in the loop body, so you can divide the code between the two however you like.
@@SECTION Tutorial.StringHandling Simple String ManipulationSome time back, we called the status() function, and extracted a result which wasn't a number at all. It was a string, which is a set of character values, one after the other. Of course, it doesn't usually look that way. It usually looks like a word or phrase or paragraph.
In DGD, just like in C, strings use what's called ASCII code. Computers represent everything with numbers internally. So every printable character (and many special characters with no printable representation) must be assigned a number. ASCII is one way to assign those numbers. A DGD string is just a sequence of numbers, each representing a letter, number or other printable character.
A DGD string can also hold the empty string, which is written as "". That's a sequence of zero characters, and it just means "no string here". The string can also hold the special value nil. Every string holds nil when you first declare it. So do certain other data types we'll talk about later. nil isn't a valid string, it's just a placeholder meaning that the string isn't usable yet — you have to assign a value to it first.
So go ahead and type code "Hello world!". You should see a result something like $15 = "Hello world!". The quotation marks around it mean that it's a string. In LPC, a string is always double-quoted.
Just as you can use operations like + and * on numbers, you can also use certain operators on strings. For instance, the + operator will concatenate two strings into a single longer one. Try typing code "Hello" + "World!". Note that you don't automatically get a space in between, so you should have seen a result like $18 = "HelloWorld!".
Remember that the string is made of a sequence of numbers which represent characters. In LPC you can extract one of these numbers with the square-brace operator. For instance, type code "A"[0]. You should see a result like $20 = 65. That's because 65 is the ASCII encoding for an uppercase A. Try using strings other than "A" and you can see more ASCII encodings. You'll notice that all the uppercase letters are ordered alphabetically within themselves so B is 66, C is 67 and so on. The lowercase letters do the same thing.
The zero in braces, above, specifies what offset in the string to examine. For instance, if you type code "FANDANGO"[4], you'll get 65 also. That's because the number is square brackets is what character of the string to examine, and character four corresponds to the fifth character, which is an uppercase A in the word "FANDANGO". The string's characters are numbered in order, starting with zero.
You can also extract more than one character from a string. But that can't just be an ASCII number. So if you extract more than one character from a string, LPC gives you another string. Try typing code "Big Bertha"[0..2]. You should get the result "Big". That's characters number zero through two — the first through third characters.
You'll find that if you substitute [..2] for
[0..2], it works exactly the same way. Leaving off the
first number means "from the beginning of the string". Similarly,
using [3..] would mean "from the fourth character to
the end of the string". Perl and some LPC dialects let you specify
negative numbers to measure from the end of the string, but DGD
does not. So you should only use positive numbers for your string
offsets.
You can also find the length of a string in characters. For instance, type code strlen("Bob's Aunt Polly"). You should get a result like $24 = 16. That's because the string contains sixteen characters — that includes the apostrophe and both spaces. Character counting with strlen() includes punctuation and other special characters as well as letters and numbers.
@@SECTION Tutorial.UserOutput Sending Output to the UserIn your code statements, you can also use a variable called user. That variable represents the current user object, which is the network connection that you have to the DGD server. There are certain functions you can call on it to do various things.
First, try typing code user->query_conn(). You should see a result like $25 = </kernel/obj/telnet#20>. This means that history variable $25 holds a clone of the /kernel/obj/telnet object. That clone is the object that DGD considers the connection object. If you type code query_ip_name(user->query_conn()), you should see either $26 = "localhost" or a similar result with your computer's hostname. If you type code query_ip_number(user->query_conn()), you should see a similar result with "127.0.0.1" or your computer's IP address. If you don't know what a hostname or IP address is, you should probably learn that before running your own MUD... Both are used by internet-connected machines to locate other internet-connected machines.
Another interesting thing you can do with the user object is to send output to it. For instance try typing user->message("Bob!\n");. You should see the word "Bob!" on a line by itself, and the history variable will have a value of nil. If you remember, nil was the value that a string has before it gets initialized. It's also the value that a function returns if you don't specifically return anything at all.
The backslash and n after "Bob!", above, are a character escape. A character escape is composed of an escape character, which is the backslash, followed by another character, in this case an 'n' for the newline character. There are certain other character escapes that have meaning, such as \b for backspace, \r for a carriage return and \t for a tab. Since the backslash and the double-quote have special meaning in strings, there are special character escapes to write them as well — \\ and \", respectively. Note that with some MUD libraries you'll need to write an end-of-line as \n\r or \r\n. So if your telnet is doing odd things at line endings, try that. You may also want to see if there's an option in your telnet client for treating line endings differently. Many telnet clients (including PuTTY on Win32) support changing the behavior with an option.
To see more of how the newline character works, try adding more \n characters. Type code user->message("Line...\n\n\n\n"). If you leave the semicolon off like that, you won't get nil returned, but rather the number 1. That's because the user->message function returns an integer, which is either 1 or 0 for whether the entire message could be sent.
When you printed a message with more than one \n, you saw more than one line of space before the returned value. That's because the user->message function writes the output directly to your terminal, and it does it immediately. So you could use it to print fancier output if you wanted to. Anything you print won't be saved to a history variable, so make sure you're okay with it only being on the screen. There's way for DGD to get it back and play with it more.
@@SECTION Tutorial.CodeInFiles Making and Using LPC FilesYou may be getting pretty tired of the code command by now. Even if you're not, you're probably starting to understand why we don't use it for big programs. Retyping all that stuff can get ugly. Typos are difficult to correct if you're retyping everything. And the code would certainly look better if it could be formatted properly.
To start using LPC files, you'll first need to be able to edit text files somehow. That means you'll need an editing program, or editor.
Since you're running your own copy of DGD, the easiest way for you to edit files is to use an editing program that's on your desktop. While it's possible to use the built-in DGD editor, it's probably easier not to. The built-in one isn't a very good editor by modern standards, and it's hard to use if you're used to normal desktop editing programs.
On Unix, whatever editor you've already got will almost certainly do. On Windows, however, you'll need to be careful because most editors put Windows-style line breaks into text files. DGD requires Unix-style line breaks in its text files. If you don't know what line breaks are, don't worry too much, but do follow the instructions here anyway. TextPad is a freely available Win32 editor that will handle line breaks properly. There are also versions of emacs and vi for Windows, and either will work. I don't know the situation on Macintosh computers, so I don't know what editors are available that will handle non-native line breaks.
It's useful to know what the code command does so you can get your code to work similarly. The code command makes a new object from a file every time you use it. That file looks something like this:
#include <float.h>
#include <limits.h>
#include <status.h>
#include <trace.h>
#include <type.h>
/* The exec() function, from the code command */
mixed exec(object user, mixed argv...) {
mixed a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z;
<Your Code>
}
It's actually a little more complicated than that — for
instance, a return statement is added before your code
if that code doesn't end with a semicolon or end-curly-brace. Also,
your code has all the history references turned into something more
palatable. The history variables aren't part of DGD's dialect of
LPC. They're only part of the Kernel Library.
To create the LPC file, you'll need to use your editor to create a file called /usr/admin/myobj.c. Note that that filename is relative to the directory of the Kernel Library, so if you installed DGD in C:\DGD, you'll need to make the file C:\DGD\mud\usr\admin\myobj.c. If you installed dgd in /home/bob/dgd, you'll need to make the file /home/bob/dgd/mud/usr/admin/myobj.c.
You should make an LPC file exactly like the above, except that
where it says "<Your Code>", you should substitute
return 7;. That way, the test object will return the
number 7 when you call its function. So you'll have created the
object that the Kernel Library would create internally if you used
the command code 7.
Now you'll need to compile the object. To do that from the admin user's command line you'll want to type compile /usr/admin/myobj.c. You may just be able to type compile myobj.c for the same result — use the pwd command to make sure you're in the /usr/admin directory.
In either case, you should see a result like $15 = </usr/admin/myobj>. If you get errors instead then your code isn't quite right or there's something wrong with the file (did you use the right name?). Look at both again carefully. If DGD can't find the file, then perhaps you didn't put it in the right place, or perhaps you didn't type the "compile" line perfectly. Check them both again. Try it until you get the result above.
Once you have, you've made a usable object. To call a function on it, you'll want to use its history entry. This command line will assume that the object was $15 (as in the paragraph above). If yours was a different history number, you should substitute your own number. Type code $15->exec(this_user()). You should get a result like $16 = 7. You've successfully returned the number 7. Success!
Go back and look at the code again. You may have realized that
the long line starting with mixed declares all those
single-letter variables I mentioned earlier. Mixed
declares a variable, just as int and float
do. A mixed variable can hold any DGD type. You
could just make all of your variables mixed rather than
bothering with all the different types. But there are some serious
disadvantages to doing that, as we'll discuss later on.
DGD doesn't actually have magic variables that are available
everywhere called 'c' and 'f' and so on. The code command provides
them. But in a regular LPC file, you'll have to declare them if you
want to use them. Constants like ST_VERSION aren't
really part of the language either. The code command has them
because of the #include directives at the start of the
source file. In your own code, you'll need to specifically include
the files for any constants you may wish to use. So if you want to
use ST_VERSION, you'll need to include status.h from
your LPC object.
There's also a weird line starting with /* and
ending with */. That line is called a
comment, and it's designed to let other programmers know
things about your code. It has no effect, so you can
comment on your own code, to humans only. A comment is
allowed to span multiple lines, or it can cover only part of a
single line.. It goes from the /* to the
*/, whether that's only part of a line, or pages and
pages.
We'll cover more about the specifics of myobj.c and what we mean by a function later on. We'll cover more about what the "code" command to call it actually did. However, in the mean time, you have a good simple way to store your code and make modifications to it instead of retyping the whole thing every time. That'll be useful for longer bits of code like what you typed in the Loops and Looping section.
@@SECTION Tutorial.Arrays ArraysAn array is a list of values, either all of the same type or of multiple types. An array makes it easy to reference values by their number — for instance, it's easy to say "give me the fourth value in the array". However, an array is slow when you add values or remove them.
Let's write a function to count digits in a string. Go ahead and change myobj.c (or make a new file) to the following:
mixed count_digits(string str) {
int *num_count;
int ctr;
object user;
num_count = allocate_int(10);
for(ctr = 0; ctr < strlen(str); ctr++) {
if((str[ctr] <= '9') &&
(str[ctr] >= '0')) {
num_count[str[ctr] - '0']++;
}
}
user = this_user();
user->message("Count: [");
for(ctr = 0; ctr < 10; ctr++) {
user->message(num_count[ctr] + " ");
}
user->message("]\n");
}
To call this file, first compile it. So you'll type compile /usr/admin/myobj.c. If you're not using myobj.c as the filename, substitute the one you're using. You should get a result like $34 = </usr/admin/myobj>. Now, using the history variable you get back (we'll pretend it's $34 here), type $34->count_digits("0123456789"), or the equivalent with your own history variable number. You should see a result like the following:
Count: [1 1 1 1 1 1 1 1 1 1 ] $36 = nil
Since the function doesn't explicitly return anything, the
history variable result is nil. But since the function
calls user->message to send output to the user
(that's you!), the list of number counts is printed out. Try
calling the function a few more times with arguments different from
"0123456789". It will count the number of occurrences of each digit
in your input.
If you look at the line in the code starting with mixed
count_digits, you'll notice that it's different from the
code in the last section. It
uses a different function name, count_digits
instead of exec. That's why you typed "count_digits"
when calling the function. You'll also notice that instead of
having object user, mixed argv... in parentheses, it
has string str. That section tells what the function
needs to do its thing. For the "return 7" function, you called it
with this_user(), a user object, in parentheses. For
count_digits, you gave a string which it then counted
numbers in.
There are two new types of variable declaration in the code to count_digits. There is a variable type called object, like mixed or int. A variable of type object will hold a compiled DGD object, such as a connection object, or the object that myobj.c compiles into. In count_digits, it holds a user object like the one you passed to the function in the last section.
You'll notice that the variable declaration for num_count starts with int, but that there's an asterisk in front of the variable name. The asterisk means that num_count isn't an integer, but rather an array of integers.
Defining num_count doesn't give it a value. Arrays, like strings, are assigned nil by default, and have to be initialized to something. One way to assign reasonable values to an array of integers is the allocate_int function, which returns an array of zeroes. The array is as long as the number in parentheses is big. For the example above, that means it returns an array of ten zeroes.
There are a couple of other new things in the code above. We'll
tackle them one at a time. The first is ++, called the
increment operator. It adds one to whatever variable you
use it on. So if a was a variable, then a++
means "add one to a".
Another is the zero in single quotes. Remember that LPC characters are represented as ASCII-encoded integers. A character in single quotes will evaluate the that character's integer. Try typing code 'A' and you should get a result of 65, just like in the section on handling strings. The 'if' statement checks to make sure that the character is a number between 0 and 9. That way letter, spaces and punctuation will be ignored.
The final new feature of the code is the square brackets, called
the array dereference operator. The idea is that for an
array, like a string, you can take a single element from it. So if
an array called arr contains a zero followed by four
nines, then the value of arr[0] is zero, and the value
of arr[1], arr[2] and arr[3]
would each be nine. If you tried to check arr[4],
you'd get an error because there are only four elements and you
tried to ask about the fifth one.
In num_count[str[ctr] - '0']++;, the square
brackets are used twice, one inside the other. The first is to
dereference a character of the string, just like before. Then
'0' is subtracted from that character. The numbers 0
through 9, when ASCII encoded, become 48 through 57. Since the
numbers are in order, starting with zero, you can subtract ASCII
zero (which equals the number 48) from an ASCII digit such as the
ones you typed in, and get a number 0 through 9 for which digit it
was. That's why we subtract the character zero from that character
of the string.
The second dereference, the one outside, is an array
dereference, not a string dereference, but they work the same way
if you think of a string as being like an array of ASCII-encoded
characters. Since subtracting the character '0' leaves
a number between 0 and 9, we can then use that character as the
offset within the array. Since the array has ten
elements, they're numbered 0 through 9.
To write an array value in LPC, you surround the entries with
parentheses and curly braces and separate the entries with commas.
So the array mentioned above would be written ({ 0, 9, 9, 9,
9 }) in LPC. Instead of the allocate_int call, you could
have written num_count = ({ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
}); The curly braces have to be inside the
parentheses, they can't be outside them.
Arrays can also be added to each other like strings. Adding them will make a new array which is all the elements of the first array followed by all the elements of the second array. So code ({ 1, 2, 3 }) + ({ 7, 8, 9 }) will print a result like $40 = ({ 1, 2, 3, 7, 8, 9 }).
@@SECTION Tutorial.Mappings MappingsMappings are data structures, rather like arrays. An array is a sequence of values (often numbers). A mapping is a sort of dictionary of values, so that you can find a value by looking up its name, called a key. You'll see mappings called hash tables in some other languages.
Let's say we wanted to look through an array of strings and make sure there were no repeated elements. Specifically, we wanted to read through an array and return another array which had no repeated elements, so every value was unique.
string *unique(string *array) {
mapping elements;
int ctr;
elements = ([ ]);
for(ctr = 0; ctr < sizeof(array); ctr++) {
elements[array[ctr]] = 1;
}
return map_indices(elements);
}
Notice that mapping is used like int to declare variables. Notice also that we have to initialize the mapping to a new empty value before we assign anything to it.
A mapping in LPC is written almost, but not quite, like an
array. The mapping ([ "a" : 1, "b" : 2, "c" : 3 ]) can
be dereferenced like an array. But instead of dereferencing by
number, this mapping is dereferenced by name. So if a mapping
called letters had that value, then
letters["a"] would have the value 1 and
letters["c"] would have the value 3. ([
]) is a fresh new empty mapping — there are no keys or
values at all in it.
Try compiling a new /usr/admin/myobj.c with the function above in it. This can be instead of the old function, or in addition to it. Once you've successfully compiled myobj.c, try typing "/usr/admin/myobj"->unique( ({ "bob", "sam", "murray", "bob" }) ). You should get a result like $45 = ({ "bob", "murray", "sam" }). The result contains all the same strings as the original, but in a different order, and with no repeated "bob". You've just called your function on the object, but you didn't use quite the same syntax you've been using. Instead, you used the object's name in double quotes. The object's name is the same as the filename, but without the .c on the end.
The function first makes a new, empty mapping, and then puts each element of the array into it as a key, also called an index. It assigns the value 1 to each key, though the value won't actually be used. Any repeated array elements will just assign 1 to the same key that's already in the array. Then, at the end, we call a function called map_indices which returns an array of the indices (keys) of the mapping, in alphabetical order.
@@SECTION Tutorial.Functions FunctionsYou've seen some functions in previous sections, but we haven't really explored how they work.
@@SECTION Tutorial.Objects Objects @@SECTION Tutorial.Arguments Arguments and Call-By-ValueIn LPC, most values are passed by value. That means if you pass an integer variable with a value of 3 into a function and you change that variable in the function, the variable doesn't change outside the function. Because of that, the following code doesn't do what it looks like it's supposed to. In fact, it does nothing.
/* Swap two integers */
void swap(int a, int b) {
int tmp;
tmp = b;
b = a;
a = b;
}
It doesn't swap the values beyond the end of the function. Internally, LPC makes the new variables for arguments a and b and copies the values in. The code swaps the values of the arguments' LPC-internal variables, but those variables are going to disappear at the end of the function. This method of function call is named call-by-value because only the value gets passed in -- the original variable is copied so the function never sees it.
LPC passes arrays, mappings and objects by reference. That means
that if you change an element in the array, it's changed
everywhere, not just in that one function. So call-by-value is only
used for integers, floating-point numbers and strings. Values of
type mixed are passed according to the underlying type
— a mixed variable with a current value of 3 or
nil will be passed by value, while one whose current
value is a mapping will be passed by reference, and might be
modified.
The fact that some objects are passed by reference has security implications. If an object in your application keeps information on all users, it will probably wish to use an array or mapping. It may wish to have a query function to return such an array or mapping to a caller, or it may wish to call functions in other objects with a list of users. But if it simply passes its array or mapping around as an argument, then other objects may modify that array or mapping, which would be highly inconvenient. Instead, the object should copy the array or mapping before returning it, and before passing it as an argument to any questionably-trusted functions. The copied array or mapping contains all the same information, but will not change the original if it is modified. For example:
void trusted_function(void) {
mixed *users;
mixed *copied_users;
users = "/usr/System/sys/secured"->get_user_array();
copied_users = users[..]; /* Make a copy */
untrusted_function(7, "sam", copied_users);
}
@@SECTION Tutorial.VariableScope External Variables and Scope