Newer
Older
<title>Pike tutorial</title>
Fredrik Noring
committed
Explain that we have bignums
An Appendix about kompiling
Running Pike on NT
Fredrik Noring
committed
sprintf does not work like it used to do since it has been updated
for bignum functionality.
The %[n]-feature is documented in the code but not here.
Fredrik Noring
committed
Reference-part about functions.
Separate the referece-part from the tutorial?
Tutorial about how the compiling of Pike-programs and
interpretation.
Fix chapter 3 to be one reference-part and one tutorial-part.
Make sure that all global functions and modules are described.
Write an appendix about old functions, behaviors and keywords.
("How to convert from old versions of Pike")
predef::m_delete-behavior is missing. - Fixed by hubbe
Missing funktions in the manual: (tab before function means fixed)
has_prefix (is it supposed to be global, or perhaps String.has_prefix? /Noring)
Regexp->replace
Fredrik Noring
committed
_sqrt
_is_type
_sprintf
_debug
_describe
_static_modules
_typeof
"`!",
"`!=",
"`%",
"`&",
"`()",
"`*",
"`+",
"`-",
"`->",
"`/",
"`<",
"`<<",
"`<=",
"`==",
"`>",
"`>=",
"`>>",
"`[]",
"`^",
"`|",
"`~", (All these should be checked for updates in 5)
all_threads (link to threads)
(chmod fixed /Mirar) (Move to system module? -Hubbe)
cleargroups
closelog
endpwent
endgrent
filesystem_stat
get_all_groups
get_all_users
get_groups_for_user
getegid need ref
geteuid need ref
getgid need ref
gethostbyaddr need ref
gethostbyname need ref
gethostname need ref
getppid
getpwent
getpwnam
getpwuid
gmtime - already exists /Hubbe
object_variablep - done /Hubbe
openlog
readlink
resolvepath
set_priority
set_weak_flag
setegid
seteuid
setgid
setgrent
setgroups
setpwent
this_program (Well, not really a function. Don't know where it belongs. /mast)
this_thread - doc exists, ptr from global list to module needed?
thread_create - doc exists, ptr from global list to module needed?
thread_local - doc exists, ptr from global list to module needed?
thread_set_concurrency
umask
uname
werror
These functions shall be removed from chapter 16:
catch (Should be a link to misc)
query_host_name (Simulate-function! Move!)
typeof (Not a function and is documented in one way in misc
and another way in 16 (with a typo `?))
"This chapter is a reference for all the builtin functions in
Pike. They are listed in alphabetical order." is not correct if not
all global functions are included (like threads and Stdio-stuff).
If the Threads och Stdio-functions begin to work in the modules,
this comment can be erased.
Process.create_process ? done -hubbe
#define now handles varargs, should be in chapter 15.
<firstpage>
<p>
<center>
<h1>
Programming, using and understanding
<p>
<p>
</h1>
<h2>by Fredrik Hübinette</h2>
</center>
</firstpage>
<preface title="Preface">
This book was written with the intention of making anybody with a little
programming experience able to use Pike. It should also be possible to
gain a deep understanding of how Pike works and to some extent why it works
the way it does from this book. It will teach you how to write your own
extensions to Pike. I have been trying for years to get someone else to
write this book, but since it seems impossible without paying a fortune for
it I will have to do it myself.
<a href="http://www.emit.com.pl/ian.html">Ian Carr-de Avelon</a> and
<a href="mailto:hedda@idonex.se">Henrik Wallin</a>
The book assumes that
you have programmed some other programming language before and that you
</preface>
<table-of-contents title="Table of contents">
<introduction title="Introduction">
This introduction will give you some background about Pike and this book
and also compare Pike with other languages. If you want to start
learning Pike immediately you can skip this chapter.
<section title="Overview">
This book is designed for people who want to
learn Pike fast. Since Pike is a simple language to learn, especially
if you have some prior programming experience, this should benefit most people.
<p>
Chapter one is devoted to background information about Pike and this book.
It is not really necessary to read this chapter to learn how to use and
program Pike, but it might help explain why some things work the way they do.
It might be more interesting to re-read the chapter after you have
learned the basics of Pike programming.
Chapter two is where the action starts. It is a crash course in Pike with
examples and explanations of some of the basics. It explains the
fundamentals of the Pike data types and control structures.
The systematic documentation of all Pike capabilities starts in chapter three
with a description of all control structures in Pike. It then continues with
all the data types in chapter four and operators in chapter five. Chapter
six deals with object orientation in Pike, which is slightly different than
<!-- FIXME (finish this overview) -->
</section>
<anchor name=uLPC>
<section title="The history of Pike">
In the beginning, there was Zork. Then a bunch of people decided to make
multi-player adventure games. One of those people was Lars Pensjö at the
Chalmers university in Gothenburg, Sweden. For his game he needed a simple,
memory-efficient language, and thus LPC (Lars Pensjö C) was born. About a
year later I started playing one of these games and found that the language
was the most easy-to-use language I had ever encountered. I liked the language
so much that I started improving it and before long I had made my own LPC
dialect called LPC4. LPC4 was still geared towards writing adventure games,
but was quite useful for writing other things with as well. A major problem
with LPC4 was the copyright. Since it was based on Lars Pensjö's code, it
came with a license that did not allow it to be used for commercial gain.
So, in 1994 I started writing µLPC, which was a new but similar LPC interpreter.
I got financial backing from Signum Support AB for writing µLPC. Signum
is a company dedicated to supporting GNU and GPL software and they wanted
to create more GPL software.
<p>
When µLPC became usable, InformationsVävarna AB started using it for
their web-server. Before then, Roxen (then called Spinner) was non-commercial
and written in LPC4. Then in 1996 I started working for InformationsVävarna
developing µLPC for them. We also changed the name of µLPC to Pike to
get a more commercially viable name.
</section>
</anchor>
<section title="A comparison with other languages">
<dl>
<dt> Python
<dd> Python is probably the language that is most like Pike. Pike is faster
and has better object orientation. It also has a syntax similar to
C++, which makes it more familiar for people who know C++. Python on
the other hand, has a lot more libraries available.
<dt> C++
<dd> Pike's syntax is almost the same as for C++. A huge difference is that
Pike is interpreted. This makes the code slower, but reduces compile times
to almost nothing. For those few applications which require the speed of
C or C++, it is often easier to write a Pike extension than to write the
whole thing in C or C++.
<dt> Lisp and Scheme
<dd> Internally Pike has a lot in common with simple interpreted Lisp and
Scheme implementations. They are all stack based, byte-compiled,
interpreted languages. Pike is also
a 'one-cell' language, just like Scheme.
<dt> Pascal
<dd> Pike has nothing in common with Pascal.
<dt> Tcl/Tk
<dd> Pike is similar to Tcl/Tk in intent and they both have good string
handling. Pike has better data types and is much faster however.
On the other hand Tcl/Tk has X windows system support.
<!-- The idea from the beginning was similar but the implementation is very different.-->
</section>
<section title="What is Pike">
Pike is:
<ul>
<li> A programming language
<li> Object oriented
<li> Interpreted
<li> Fast
<li> Dynamic
<li> High-level
<li> similar to C++
<li> easy to extend
<li> (a fish)
</ul>
Pike has:
<ul>
<li> Garbage collection
<li> Advanced string functions
<li> 6 years of development behind it
<li> Advanced data types such as associative arrays
<li> Support for bignums
<li> Builtin socket support
</ul>
</section>
<section title="How to read this manual">
This manual uses a couple of different typefaces to describe different
things:
<dl>
<dt><i>italics</i>
<dd>Italics is used as a placeholder for other things. If it says <i>a word</i>
in the text it means that you should put your own word there.
<dt><b>bold</b>
<dd>Bold is just used to emphasize that this word is not merely what it sounds
like. It is actually a <b>term</b>.
<dt><tt>fixed size</tt>
<dd>Fixed size is used to for examples and text directly from the computer.
Also, please beware that the word <b>program</b> is also a builtin Pike
</section>
</introduction>
<chapter title="Getting started">
<p>
First you need to have Pike installed on your computer. See <ref to=install>
for the first of the following examples that the Pike binary is in your UNIX search
path. If you have problems with this, consult the manual for your shell
or go buy a beginners book about UNIX.
<p>
<section title="Your first Pike program">
<example language=pike>
int main()
{
write("hello world\n");
return 0;
}
</example>
Let's call this file hello_world.pike, and then we try to run it:
<p>
<pre>
$ pike hello_world.pike
hello world
$
</pre>
Pretty simple, Let's see what everything means:
<example language=pike>
</example>
This begins the function <tt>main</tt>. Before the function name the type of value
it returns is declared, in this case <tt>int</tt> which is the name of the
integer number type in Pike. The empty space between the
parenthesis indicates that this function takes no arguments.
A Pike program has to contain at least one function, the <tt>main</tt> function. This function is where program execution starts and thus the function from which every other function is called, directly or indirectly. We can say that this function is called by the operating system.
Pike is, as many other programming languages, built upon the concept of functions, i.e. what the program does is separated into small portions, or functions, each performing one (perhaps very complex) task. A function declaration consists of certain essential components; the type of the value it will return, the <i>name</i> of the function, the <i>parameters</i>, if any, it takes and the body of the function. A function is also a part of something greater; an object. You can program in Pike without caring about objects, but the programs you write will in fact be objects themselves anyway.
Now let's examine the body of <tt>main</tt>;
<p>
<example language=pike>
{
write("hello world\n");
return 0;
}
</example>
Within the function body, programming instructions, statements, are grouped together in blocks. A block is a series of statements placed between curly brackets. Every statement has to end in a semicolon. This group of statements will
be executed every time the function is called.
<p>
<example language=pike>
write("hello world\n");
</example>
The first statement is a call to the builtin function <tt>write</tt>. This will
execute the code in the function <tt>write</tt> with the arguments as input data.
In this case, the constant string <tt>hello world\n</tt> is sent.
Well, not quite. The <tt>\n</tt> combination corresponds to the newline
character.
<tt>write</tt> then writes this string to stdout when executed. Stdout is the standard Unix output channel, usually the screen.
<p>
<example language=pike>
</example>
This statement exits the function and returns the value zero. Any statements
following the return statements will not be executed.
<p>
</section>
<section title="Improving hello_world.pike">
Typing <tt>pike hello_world.pike</tt> to run our program may seem a bit
unpractical. Fortunately, Unix provides us with a way of automating this
somewhat. If we modify hello_world.pike to look like this:
<p>
#!/usr/local/bin/pike
int main()
{
write("hello world\n");
return 0;
}
</example>
And then we tell UNIX that hello_world.pike is executable so we can run
hello_world.pike without having to bother with running Pike:
<p>
<pre>
$ chmod +x hello_world.pike
$ ./hello_world.pike
hello world
$
</pre>
N.B.: The hash bang (#!) must be first in the file, not even whitespace is allowed to precede it!
The file name after the hash bang must also be the complete file name to the Pike binary, and it may not exceed 30 characters.
<p>
</section>
<section title="Further improvements">
Now, wouldn't it be nice if it said <tt>Hello world!</tt> instead of <tt>hello world</tt> ?
But of course we don't want to make our program "incompatible" with the old
version. Someone might need the program to work like it used to.
Therefore we'll add a <i>command line option</i> that will make it type the old
<tt>hello world</tt>. We also have to give the program the ability to choose
what it should output based on the command line option.
This is what it could look like:
<p>
<example language=pike>
#!/usr/local/bin/pike
int main(int argc, array(string) argv)
{
if(argc > 1 && argv[1]=="--traditional")
{
write("hello world\n"); // old style
}else{
write("Hello world!\n"); // new style
}
return 0;
}
</example>
Let's run it:
<p>
<pre>
$ chmod +x hello_world.pike
$ ./hello_world.pike
Hello world!
$ ./hello_world.pike --traditional
hello world
$
</pre>
What is new in this version, then?
<example language=pike>
int main(int argc, array(string) argv)
</example>
In this version the space between the parenthesis has been filled.
What it means is that <tt>main</tt> now takes two arguments.
One is called <tt>argc</tt>, and is of the type <tt>int</tt>.
The other is called <tt>argv</tt> and is a an array of strings.
<p>
The arguments to <tt>main</tt> are taken from the command line when the
Pike program is executed. The first argument, <tt>argc</tt>, is how many
words were written on the command line (including the command itself) and
<tt>argv</tt> is an array formed by these words.
<p>
<example language=pike>
if(argc > 1 && argv[1] == "--traditional")
{
write("hello world\n"); // old style
}else{
write("Hello world!\n"); // new style
}
</example>
This is an if-else statement, it will execute what's between the first set
of brackets if the expression between the parenthesis evaluate to something
other than zero. Otherwise what's between the second set of brackets will
be executed. Let's look at that expression:
<p>
<example language=pike>
argc > 1 && argv[1] == "--traditional"
</example>
Loosely translated, this means: argc is greater than one, and the second
element in the array argv is equal to the string <tt>--traditional</tt>. Since
argc is the number of words on the command line the first part is true only
if there was anything after the program invocation.
<p>
Also note the comments:
<p>
<example language=pike>
write("hello world\n"); // old style
</example>
The <tt>//</tt> begins a comment which continues to the end of the line.
Comments will be ignored by the computer when it reads the code.
This allows to inform whoever might read your code (like yourself) of
what the program does to make it easier to understand.
Comments are also allowed to look like C-style comments, i.e. <tt>/* ... */</tt>, which can extend over several lines. The <tt>//</tt> comment only extends to the end of the line.
<p>
</section>
<section title="Control structures">
The first thing to understand about Pike is that just like any other
programming language it executes one piece of code at a time. Most of
the time it simply executes code line by line working its way downwards.
Just executing a long list of instructions is not enough to make an interesting
program however. Therefore we have <b>control structures</b> to make Pike
execute pieces of code in more interesting orders than from top to bottom.
<p>
We have already seen an example of the <tt>if</tt> statement:
<example language=pike meta=expression,statement1,statement2>
if( expression )
statement1;
else
statement2;
<!-- FIXME: should if have a capital letter or not? -->
<tt>if</tt> simply evaluates the expression and if the result is true it
executes <i>statement1</i>, otherwise it executes <i>statement2</i>. If you have no need for
statement2 you can leave out the whole else<!-- FIXME: tt/italics --> part like this:
<example language=pike meta=expression,statement1>
if( expression )
statement1;
In this case <i>statement1</i> is evaluated if <i>expression</i> is true, otherwise
nothing is evaluated.
<p>
<b>Note for beginners: go back to our first example and make sure you
understand what <tt>if</tt> does.</b>
<p>
Another very simple control structure is the <tt>while</tt> statement:
<example language=pike meta=expression,statement>
while( expression )
statement;
This statement evaluates <i>expression</i> and if it is found to be true it
evaluates <i>statement</i>. After that it starts over and evaluates <i>expression</i>
again. This continues until <i>expression</i> is no longer true. This type of
control structure is called a <b>loop </b> and is fundamental to all
interesting programming.
<p>
</section>
<section title="Functions">
Another control structure we have already seen is the function.
A function is simply a block of Pike code that can be executed with different arguments from different places in the program.
A function is declared like this:
<example language=pike meta=modifiers,type,varname1,varname2,statements>
modifiers type name(type varname1, type varname2, ...)
{
statements
}
The <i>modifiers</i> are optional. See <ref to=modifiers> for more details about
modifiers. The <i>type </i> specifies what kind of data the function returns.
For example, the word <tt>int</tt> would signify that the function returns
an integer number. The <i>name </i> is used to identify the function when
calling it. The names between the parenthesis are the arguments to the
function. They will be defined as local variables inside the function. Each
variable will be declared to contain values of the preceding type.
The three dots signifies that you can have anything from zero to 256 arguments
to a function. The <i> statements </i> between the brackets are the function
body. Those statements will be executed whenever the function is called.
<p>
<b>Example:</b>
<example language=pike>
int sqr(int x) { return x*x; }
</example>
This line defines a function called <tt>sqr</tt> to take one argument of the
type <tt>int</tt> and also returns an <tt>int</tt>. The code itself returns
the argument multiplied by itself. To call this function from somewhere in the code
you could simply put: <tt>sqr(17)</tt> and that would return the integer value
289.
<p>
As the example above shows, <tt>return</tt> is used to specify the
return value of a function. The value after <tt>return</tt> must be of the
type specified before the function name. If the function is specified to
return <tt>void</tt>, nothing at all should be written after <tt>return</tt>.
Note that when a return statement is executed, the function will finish
immediately. Any statements following the return will be ignored.
<p>
There are many more control structures, they will all be described in a
later chapter devoted only to control structures.
<p>
</section>
<section title="True and false">
Throughout this chapter the words <b>true</b> and <b>false</b> have been used
without any explanation to what they mean. Pike has a fairly simple way of
looking at this. The number 0 is false and everything else is true.
(Except when using operator overloading as I will explain in a later chapter.)
<p>
</section>
<section title="Data Types">
As you saw in our first examples we have to indicate the type of value returned by a function or contained in a variable. We used integers (<tt>int</tt>), strings (<tt>string</tt>), and arrays (with the * notation).
The others are <tt>mapping</tt>, <tt>mixed</tt>, <tt>void</tt>, <tt>float</tt>, <tt>multiset</tt>, <tt>function</tt>, <tt>object</tt> and <tt>program</tt>.
Neither <tt>mixed</tt> nor <tt>void</tt> are really types, <tt>void</tt> signifies that no value should be returned and <tt>mixed</tt> that the return value can be of any type, or that the variable can contain any type of value.
<tt>Function</tt>, <tt>object</tt> and <tt>program</tt> are all types related to object orientation. We will not discuss the last three in any great detail here,
<dd>The integer type stores a signed integer. It is 32 bit or 64 depending
on architecture.
<dt>Float
<dd>This variable type stores a floating point number.
<dt>Array
<dd>Arrays are basically a place to store a number of other values.
Arrays in Pike are allocated blocks of values.
They are dynamically allocated and do not need to be declared as in C.
The values in the array can be set when creating the array like this:
<example language=pike>
</example>
Or, if you have already created an array, you can change the values
in the array like this:
<example language=pike meta=arr,ind,data>
</example>
This sets entry number <i>ind</i> in the array <i>arr</i> to <i>data</i>.
<i>ind</i> must be an integer.
The first index of an array is 0 (zero). A negative index will count from the end of the array rather than from the beginning, -1 being the last element.
To declare that a variable is an array we simply type <tt>array</tt> in
front of the variable name we want:
<example language=pike>
</example>
We can also declare several array variables on the same line:
<example language=pike>
</example>
If we want to specify that the variable should hold an array of strings,
we would write:
<example language=pike>
</example>
<dd>A string contains a sequence of characters, a text, i.e. a word, a sentence or a book.
Note that this is not simply the letters A to Z; special characters, null characters, newlines and so on can all be stored in a string.
Any 8-bit character is allowed.
String is a basic type in Pike, it is not an array of char like it is in C.
This means that you cannot assign new values to individual characters in a string.
Also, all strings are "shared", i.e. if the same string is used in several places, it will be stored in memory only once.
When writing a string in a program, you enclose it in double quotes. To write special characters you need to use the following syntax:
Fredrik Hübinette (Hubbe)
committed
<tr><td>\n</td><td>newline</td></tr>
<tr><td>\r</td><td>carriage return</td></tr>
<tr><td>\t</td><td>tab</td></tr>
<tr><td>\b</td><td>backspace</td></tr>
<tr><td>\"</td><td>" (quotation character)</td></tr>
<tr><td>\\</td><td>\ (literal backslash)</td></tr>
<dd>A mapping is basically an array that can be indexed on any type, not just integers. It can also be seen as a way of linking data (usually strings) together. It consists of a lot of index-data pairs which are linked together in such a way that map[index1] returns data1.
A mapping can be created in a way similar to arrays:
<example language=pike>
mapping(string:string) map=(["five":"good", "ten":"excellent"]);
</example>
You can also set that data by writing map["five"]="good".
If you try to set an index in a mapping that isn't already present in the mapping it will be added as well.
<dt>Multiset
<dd>A multiset is basically a mapping without data values. When referring to an index in the multiset a 1 (one) will be returned if the index is present and 0 (zero) otherwise.
</dl>
</section>
</chapter>
<chapter title="A more elaborate example">
<!-- FIXME: explain things AFTER showing the code? -->
To illustrate several of the fundamental points of Pike we will now
introduce an example program, that will be extended as we go.
We will build a database program that keeps track of a record
collection and the songs on the records. In the first version we
hard-code our "database" into the program. The database is a mapping
where the index is the record name and the data is an array of strings.
The strings are of course the song names. The default register consists
of one record.
<example language=pike>
#!/usr/local/bin/pike
mapping (string:array(string)) records =
([
"Star Wars Trilogy" : ({
"Fox Fanfare",
"Main Title",
"Princess Leia's Theme",
"Here They Come",
"The Asteroid Field",
"Yoda's Theme",
"The Imperial March",
"Parade of the Ewoks",
"Luke and Leia",
"Fight with Tie Fighters",
"Jabba the Hut",
"Darth Vader's Death",
"The Forest Battle",
"Finale"
})
]);
</example>
We want to be able to get a simple list of the records in our database. The function <tt>list_records</tt> just goes through the mapping <tt>records</tt> and puts the indices, i.e. the record names, in an array of strings, record_names. By using the builtin function <tt>sort</tt> we put the record names into the array in alphabetical order which might be a nice touch.
For the printout we just print a header, "Records:", followed by a newline. Then we use the loop control structure <tt>for</tt> to traverse the array and print every item in it, including the number of the record, by counting up from zero to the last item of the array. The builtin function <tt>sizeof</tt> gives the number of items in an array. The printout is formatted through the use of <tt>sprintf</tt> which works more or less like the C function of the same name.
<example language=pike>
void list_records()
{
int i;
array (string) record_names=sort(indices(records));
write("Records:\n");
for(i=0;i<sizeof(record_names);i++)
write(sprintf("%3d: %s\n", i+1, record_names[i]));
}
</example>
If the command line contained a number our program will find the record of that number and print its name along with the songs of this record. First we create the same array of record names as in the previous function, then we find the name of the record whose number (<tt>num</tt>) we gave as an argument to this function. Next we put the songs of this record in the array <tt>songs</tt> and print the record name followed by the songs, each song on a separate line.
<example language=pike>
void show_record(int num)
{
int i;
array (string) record_names = sort(indices (records));
string name=record_names[num-1];
array (string) songs=records[name];
write(sprintf("Record %d, %s\n",num,name));
for(i=0;i<sizeof(songs);i++)
write(sprintf("%3d: %s\n", i+1, songs[i]));
}
</example>
The main function doesn't do much; it checks whether there was anything on the command line after the invocation.
If this is not the case it calls the list_records function, otherwise it sends the given argument to the show_record function.
When the called function is done the program just quits.
<example language=pike>
int main(int argc, array (string) argv)
{
if(argc <= 1)
{
list_records();
} else {
show_record((int) argv[1]);
}
}
</example>
<section title="Taking care of input">
Now, it would be better and more general if we could enter more records into our database. Let's add such a function and modify the <tt>main()</tt> function to accept "commands".
<p>
<section title="add_record()">
Using the method <tt>Stdio.Readline()->read()</tt> we wait for input which will be put into the variable <tt>record_name</tt>. The argument to <tt>->read()</tt> is printed as a prompt in front of the user's input. Readline takes everything up to a newline character.
Now we use the control structure <tt>while</tt> to check whether we should continue inputting songs.
The <tt>while(1)</tt> means "loop forever", because 1 is always <b>true</b>.
This program does not in fact loop forever, because it uses <tt>return</tt>
to exit the function from within the loop when you type a period.
When something has been read into the variable song it is checked.
If it is a "." we return a null value that will be used in the while statement to indicate that it is not ok to continue asking for song names.
If it is not a dot, the string will be added to the array of songs for this record, unless it's an empty string.
Note the <tt>+=</tt> operator. It is the same as saying
<tt>records[record_name]=records[record_name]+({song})</tt>.
<example language=pike>
void add_record()
{
string record_name=Stdio.Readline()->read("Record name: ");
records[record_name]=({});
write("Input song names, one per line. End with '.' on its own line.\n");
while(1)
{
string song;
song=Stdio.Readline()->read(sprintf("Song %2d: ",
sizeof(records[record_name])+1));
if(song==".")
return;
if (strlen(song))
records[record_name]+=({song});
}
}
</example>
</section>
<section title="main()">
The main function now does not care about any command line arguments.
Instead we use <tt>Stdio.Readline()->read()</tt> to prompt the user for instructions
and arguments. The available instructions are "add", "list" and "quit".
What you enter into the variables <tt>cmd</tt> and <tt>args</tt> is checked in the
<tt>switch()</tt> block. If you enter something that is not covered
in any of the case statements the program just silently ignores it and
asks for a new command.
In a <tt>switch()</tt> the argument (in this case <tt>cmd</tt>) is checked in the <tt>case</tt> statements. The first case where the expression equals <tt>cmd</tt> then executes the statement after the colon. If no expression is equal, we just fall through without any action.
The only command that takes an argument is "list" which works as in the first version of the program.
If "list" receives an argument, that record is shown along with all the songs
on it. If there is no argument it shows a list of the records in the database.
When the program returns from either of the listing functions, the <tt>break</tt> instruction tells the program to jump out of the <tt>switch()</tt> block.
"add" of course turns control over to the function described above.
If the command given is "quit" the <tt>exit(0)</tt> statement stops the execution of the program and returns 0 (zero) to the operating system, telling it that everything was ok.
<example language=pike>
int main(int argc, array(string) argv)
{
string cmd;
while(cmd=Stdio.Readline()->read("Command: "))
{
string args;
sscanf(cmd,"%s %s",cmd,args);
switch(cmd)
{
case "list":
if((int)args)
{
show_record((int)args);
} else {
list_records();
}
break;
case "quit":
exit(0);
case "add":
add_record();
break;
}
}
}
</example>
</section>
</section>
<section title="Communicating with files">
Now if we want to save the database and also be able to retrieve previously stored data we have to communicate with the environment, i.e. with files on disk.
Now we will introduce you to programming with objects.
To open a file for reading or writing we will use one of the programs which is builtin in Pike called <tt>Stdio.File</tt>.
To Pike, a program is a data type which contains code, functions and variables.
A program can be <i>cloned</i> which means that Pike creates a data area
in memory for the program, places a reference to the program in the data area, and initializes it to act on the data in question. The methods (i.e. functions in the object) and variables in the object Stdio.File enable us to perform actions on the associated data file.
The methods we need to use are open, read, write and close. See <ref to=io>
for more details. <!-- Does this link work? -->
<p>
<section title="save()">
First we clone a <tt>Stdio.File</tt> program to the object <tt>o</tt>.
Then we use it to open the file whose<!-- FIXME: which instead of whose? --> name is given in the string file_name for writing.
We use the fact that if there is an error during opening, open() will return a false value which we can detect and act upon by exiting.
The arrow operator (->) is what you use to access methods and variables in an object.
If there is no error we use yet another control structure, <tt>foreach</tt>, to go through the mapping <tt>records</tt> one record at a time.
We precede record names with the string "Record: " and song names with "Song: ".
We also put every entry, be it song or record, on its own line by adding a newline to everything we write to the file.<br>
Finally, remember to close the file.
<example language=pike>
void save(string file_name)
{
string name, song;
Stdio.File o=Stdio.File();
if(!o->open(file_name,"wct"))
{
write("Failed to open file.\n");
return;
}
foreach(indices(records),name)
{
o->write("Record: "+name+"\n");
foreach(records[name],song)
o->write("Song: "+song+"\n");
}
o->close();
}
</example>
</section>
<section title="load()">
The <tt>load</tt> function begins much the same, except we open the file named <tt>file</tt> for reading instead.
When receiving data from the file we put it in the string <tt>file_contents</tt>.
The absence of arguments to the method o->read means that the reading should not end until the end of the file.
After having closed the file we initialize our database, i.e. the mapping records. Then we have to put <tt>file_contents</tt> into the mapping and we do this by splitting the string on newlines (cf. the split operator in Perl) using the division operator. Yes, that's right: by dividing one string with another we can obtain an array consisting of parts from the first. And by using a <tt>foreach</tt> statement we can take the string <tt>file_contents</tt> apart piece by piece, putting each piece back in its proper place in the mapping records.
<example language=pike>
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
void load(string file_name)
{
string name="ERROR";
string file_contents,line;
Stdio.File o=Stdio.File();
if(!o->open(file_name,"r"))
{
write("Failed to open file.\n");
return;
}
file_contents=o->read();
o->close();
records=([]);
foreach(file_contents/"\n",line)
{
string cmd, arg;
if(sscanf(line,"%s: %s",cmd,arg))
{
switch(lower_case(cmd))
{
case "record":
name=arg;
records[name]=({});
break;
case "song":
records[name]+=({arg});
break;
}
}
}
}
</example>
</section>
<section title="main() revisited">
<tt>main()</tt> remains almost unchanged, except for the addition of two case statements with which we now can call the load and save functions. Note that you must provide a filename to load and save, respectively, otherwise they will return an error which will crash the program.
<example language=pike>
case "save":
save(args);
break;
case "load":
load(args);
break;
</example>
<p>
</section>
</section>
<section title="Completing the program">
Now let's add the last functions we need to make this program useful: the ability to delete entries and search for songs.
<p>
<section title="delete()">
If you sell one of your records it might be nice to able to delete that entry from the database. The delete function is quite simple.
First we set up an array of record names (cf. the <tt>list_records</tt> function).
Then we find the name of the record of the number <tt>num</tt> and use the builtin function <tt>m_delete()</tt> to remove that entry from <tt>records</tt>.
<example language=pike>
void delete_record(int num)
{
array(string) record_names=sort(indices(records));
string name=record_names[num-1];
m_delete(records,name);
}
</example>
</section>
<section title="search()">
Searching for songs is quite easy too. To count the number of hits we declare the variable <tt>hits</tt>. Note that it's not necessary to initialize variables, that is done automatically when the variable is declared if you do not do it explicitly. To be able to use the builtin function <tt>search()</tt>, which searches for the presence of a given string inside another, we put the search string in lowercase and compare it with the lowercase version of every song. The use of <tt>search()</tt> enables us to search for partial song titles as well.
When a match is found it is immediately written to standard output with the record name followed by the name of the song where the search string was found and a newline.
If there were no hits at all, the function prints out a message saying just that.
<example language=pike>
void find_song(string title)
{
string name, song;
int hits;
title=lower_case(title);
foreach(indices(records),name)
{
foreach(records[name],song)
{
if(search(lower_case(song), title) != -1)
{
write(name+"; "+song+"\n");
hits++;
}
}
}
if(!hits) write("Not found.\n");
}
</example>
</section>
<section title="main() again">
Once again <tt>main()</tt> is left unchanged, except for yet another two case statements used to call the <tt>search()</tt> and <tt>delete</tt> functions, respectively. Note that you must provide an argument to delete or it will not work properly.
<example language=pike>
case "delete":
delete_record((int)args);
break;
case "search":
find_song(args);
break;
</example>