Pike
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 what you might be used to.
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.
First you need to have Pike installed on your computer. See appendix E "How to install Pike" if this is not already done. It is also vital 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.
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.{
write("hello world\n");
return 0;
}
The first statement is a call to the builtin function write. This will execute the code in the function write with the arguments as input data. In this case, the constant string hello world\n is sent. Well, not quite. The \n combination corresponds to the newline character. write then writes this string to stdout when executed. Stdout is the standard Unix output channel, usually the screen.write("hello world\n");
This statement exits the function and returns the value zero. Any statements following the return statements will not be executed.return 0;
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:#!/usr/local/bin/pike
int main()
{
write("hello world\n");
return 0;
}
$ chmod +x hello_world.pike $ ./hello_world.pike hello world $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.
Let's run it:#!/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;
}
$ chmod +x hello_world.pike $ ./hello_world.pike Hello world! $ ./hello_world.pike --traditional hello world $What is new in this version, then?
In this version the space between the parenthesis has been filled. What it means is that main now takes two arguments. One is called argc, and is of the type int. The other is called argv and is a an array of strings.int main(int argc, array(string) argv)
The arguments to main are taken from the command line when the Pike program is executed. The first argument, argc, is how many words were written on the command line (including the command itself) and argv is an array formed by these words.
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:if(argc > 1 && argv[1] == "--traditional")
{
write("hello world\n"); // old style
}else{
write("Hello world!\n"); // new style
}
Loosely translated, this means: argc is greater than one, and the second element in the array argv is equal to the string --traditional. 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.argc > 1 && argv[1] == "--traditional"
Also note the comments:
The // 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. /* ... */, which can extend over several lines. The // comment only extends to the end of the line.write("hello world\n"); // old style
We have already seen an example of the if statement:
if simply evaluates the expression and if the result is true it executes statement1, otherwise it executes statement2. If you have no need for statement2 you can leave out the whole else part like this:if( expression )
statement1;
else
statement2;
In this case statement1 is evaluated if expression is true, otherwise nothing is evaluated.if( expression )
statement1;
Note for beginners: go back to our first example and make sure you understand what if does.
Another very simple control structure is the while statement:
This statement evaluates expression and if it is found to be true it evaluates statement. After that it starts over and evaluates expression again. This continues until expression is no longer true. This type of control structure is called a loop and is fundamental to all interesting programming.while( expression )
statement;
The modifiers are optional. See section 6.8 "Modifiers" for more details about modifiers. The type specifies what kind of data the function returns. For example, the word int would signify that the function returns an integer number. The name 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 statements between the brackets are the function body. Those statements will be executed whenever the function is called.modifiers type name(type varname1, type varname2, ...)
{
statements
}
Example:
This line defines a function called sqr to take one argument of the type int and also returns an int. The code itself returns the argument multiplied by itself. To call this function from somewhere in the code you could simply put: sqr(17) and that would return the integer value 289.int sqr(int x) { return x*x; }
As the example above shows, return is used to specify the return value of a function. The value after return must be of the type specified before the function name. If the function is specified to return void, nothing at all should be written after return. Note that when a return statement is executed, the function will finish immediately. Any statements following the return will be ignored.
There are many more control structures, they will all be described in a later chapter devoted only to control structures.
Or, if you have already created an array, you can change the values in the array like this:arr=({1,2,3});
This sets entry number ind in the array arr to data. ind 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 array in front of the variable name we want:arr [ ind ] = data;
We can also declare several array variables on the same line:array i;
If we want to specify that the variable should hold an array of strings, we would write:array i, j;
array (string) i;
| \n | newline |
| \r | carriage return |
| \t | tab |
| \b | backspace |
| \" | " (quotation character) |
| \\ | \ (literal backslash) |
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.mapping(string:string) map=(["five":"good", "ten":"excellent"]);
We want to be able to get a simple list of the records in our database. The function list_records just goes through the mapping records and puts the indices, i.e. the record names, in an array of strings, record_names. By using the builtin function sort 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 for 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 sizeof gives the number of items in an array. The printout is formatted through the use of sprintf which works more or less like the C function of the same name.#!/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"
})
]);
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 (num) we gave as an argument to this function. Next we put the songs of this record in the array songs and print the record name followed by the songs, each song on a separate line.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]));
}
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.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]));
}
int main(int argc, array (string) argv)
{
if(argc <= 1)
{
list_records();
} else {
show_record((int) argv[1]);
}
}
2.1.1 add_record()
Using the method Stdio.Readline()->read() we wait for input which will be put into the variable record_name. The argument to ->read() 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 while to check whether we should continue inputting songs.
The while(1) means "loop forever", because 1 is always true.
This program does not in fact loop forever, because it uses return
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 += operator. It is the same as saying
records[record_name]=records[record_name]+({song}).
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});
}
}2.1.2 main()
The main function now does not care about any command line arguments.
Instead we use Stdio.Readline()->read() to prompt the user for instructions
and arguments. The available instructions are "add", "list" and "quit".
What you enter into the variables cmd and args is checked in the
switch() 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 switch() the argument (in this case cmd) is checked in the case statements. The first case where the expression equals cmd 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 break instruction tells the program to jump out of the switch() block.
"add" of course turns control over to the function described above.
If the command given is "quit" the exit(0) statement stops the execution of the program and returns 0 (zero) to the operating system, telling it that everything was ok.
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;
}
}
}
2.2.1 save()
First we clone a Stdio.File program to the object o.
Then we use it to open the file 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, foreach, to go through the mapping records 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.
Finally, remember to close the file.
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();
}2.2.2 load()
The load function begins much the same, except we open the file named file for reading instead.
When receiving data from the file we put it in the string file_contents.
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 file_contents 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 foreach statement we can take the string file_contents apart piece by piece, putting each piece back in its proper place in the mapping records.
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;
}
}
}
}2.2.3 main() revisited
main() 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.
case "save":
save(args);
break;
case "load":
load(args);
break;
2.3.1 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 list_records function).
Then we find the name of the record of the number num and use the builtin function m_delete() to remove that entry from records.
void delete_record(int num)
{
array(string) record_names=sort(indices(records));
string name=record_names[num-1];
m_delete(records,name);
}2.3.2 search()
Searching for songs is quite easy too. To count the number of hits we declare the variable hits. 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 search(), 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 search() 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.
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");
}2.3.3 main() again
Once again main() is left unchanged, except for yet another two case statements used to call the search() and delete functions, respectively. Note that you must provide an argument to delete or it will not work properly.
case "delete":
delete_record((int)args);
break;
case "search":
find_song(args);
break;
|
|
One of the case statements in the above example differs in that it is
a range. In this case, any value between constant3 and
constant4 will cause Pike to jump to statement3. Note
that the ranges are inclusive, so the values constant3 and
constant4 are also valid.
3.1.1 if
The simplest one is called the if statement. It can be written anywhere
where a statement is expected and it looks like this:
if( expression )
statement1;
else
statement2;
Please note that there is no semicolon after the parenthesis or after the
else. Step by step, if does the following:
This is actually more or less how the interpreter executes the if statement.
In short, statement1 is executed if expression is true
otherwise statement2 is executed. If you are interested in
having something executed if the expression is false you can drop the
whole else part like this:
If on the other hand you are not interested in evaluating something if the
expression is false you should use the not operator to negate
the true/false value of the expression. See chapter 5 for more information
about the not operator. It would look like this:
if( expression )
statement1;
Any of the statements here and in the rest of this chapter can
also be a block of statements. A block is a list of statements,
separated by semicolons and enclosed by brackets. Note that you should
never put a semicolon after a block of statements. The example above
would look like this;
if( ! expression )
statement2 ;if ( ! expression )
{
statement;
statement;
statement;
}3.1.2 switch
A more sophisticated condition control structure is the switch
statement.
A switch lets you select one of many choices depending on the value of an
expression and it can look something like this:
As you can see, a switch statement is a bit more complicated than an
if statement. It is still fairly simple however. It starts by evaluating
the expression it then searches all the case statements in the
following block. If one is found to be equal to the value returned by
the expression, Pike will continue executing the code directly following
that case statement. When a break is encountered Pike
will skip the rest of the code in the switch block and continue executing
after the block. Note that it is not strictly necessary to have a break
before the next case statement. If there is no break before the next case
statement Pike will simply continue executing and execute the code after
that case statement as well.
switch ( expression )
{
case constant1:
statement1;
break;
case constant2:
statement2;
break;
case constant3 .. constant4:
statement3;
break;
default:
statement5;
}
The difference in how it works isn't that big either, the statement is executed if the expression is true. Then the expression is evaluated again, and if it is true the statement is executed again. Then it evaluates the expression again and so forth... Here is an example of how it could be used:while ( expression )
statement;
This would call show_record with the values 1, 2, 3 and 4.int e=1;
while(e<5)
{
show_record(e);
e=e+1;
}
For does the following steps:for ( initializer_statement ; expression ; incrementor_expression )
statement ;
for(int e=1; e<5; e=e+1)
show_record(e);
As usual, the statement can also be a block of statements, and then you do not need a semicolon after it. To clarify, this statement executes statement first, and then evaluates the expression. If the expression is true it executes the loop again. For instance, if you want to make a program that lets your modem dial your Internet provider, it could look something like this:do
statement;
while ( expression );
This example assumes you have written something that can communicate with the modem by using the functions write and gets.do {
modem->write("ATDT441-9109\n"); // Dial 441-9109
} while(modem->gets()[..6]] != "CONNECT");
We have already seen an example of foreach in the find_song function in chapter 2. What foreach does is:foreach ( array_expression, variable )
statement ;
array tmp1= array_expression;
for ( tmp2 = 0; tmp2 < sizeof(tmp1); tmp2++ )
{
variable = tmp1 [ tmp2 ];
statement;
}
while(1)
{
string command=Stdio.Readline()->read("> ");
if(command=="quit") break;
do_command(command);
}
This way, do_command will never be called with an empty string as argument.while(1)
{
string command=Stdio.Readline()->read("> ");
if(strlen(command) == 0) continue;
if(command=="quit") break;
do_command(command);
}
This would return the error code 1 to the system when the program is run.#!/usr/local/bin/pike
int main()
{
return 1;
}
|
|
Integers are coded in 2-complement and overflows are silently ignored
by Pike. This means that if your integers are 32-bit and you add 1 to
the number 2147483647 you get the number -2147483648. This works exactly
as in C or C++.
All the arithmetic, bitwise and comparison operators can be used on integers.
Also note these functions:
All the arithmetic and comparison operators can be used on floats.
Also, these functions operates on floats:
You might be surprised to see that individual characters can have values
up to 2³²-1 and wonder how much memory that use. Do not worry, Pike
automatically decides the proper amount of memory for a string, so all
strings with character values in the range 0-255 will be stored with
one byte per character. You should also beware that not all functions
can handle strings which are not stored as one byte per character, so
there are some limits to when this feature can be used.
Although strings are a form of arrays, they are immutable. This means that
there is no way to change an individual character within a string without
creating a new string. This may seem strange, but keep in mind that strings
are shared, so if you would change a character in the string "foo",
you would change *all* "foo" everywhere in the program.
However, the Pike compiler will allow you to to write code like you could
change characters within strings, the following code is valid and works:
4.1 Basic types
The basic types are int, float and string.
For you who are accustomed to C or C++, it may seem odd that a string
is a basic type as opposed to an array of char, but it is surprisingly
easy to get used to.
4.1.1 int
Int is short for integer, or integer number. They are normally
32 bit integers, which means that they are in the range -2147483648 to
2147483647. Note that on some machines an int might be larger
than 32 bits. Since they are integers, no decimals are allowed. An integer
constant can be written in several ways:
All of the above represent the number 78. Octal notation means that
each digit is worth 8 times as much as the one after. Hexadecimal notation
means that each digit is worth 16 times as much as the one after.
Hexadecimal notation uses the letters a, b, c, d, e and f to represent the
numbers 10, 11, 12, 13, 14 and 15. The ASCII notation gives the ASCII
value of the character between the single quotes. In this case the character
is N which just happens to be 78 in ASCII.
78 // decimal number
0116 // octal number
0x4e // hexadecimal number
'N' // Ascii character4.1.2 float
Although most programs only use integers, they are unpractical when doing
trigonometric calculations, transformations or anything else where you
need decimals. For this purpose you use float. Floats are normally
32 bit floating point numbers, which means that they can represent very large
and very small numbers, but only with 9 accurate digits. To write a floating
point constant, you just put in the decimals or write it in the exponential
form:
Of course you do not need this many decimals, but it doesn't hurt either.
Usually digits after the ninth digit are ignored, but on some architectures
float might have higher accuracy than that. In the exponential form,
e means "times 10 to the power of", so 1.0e9 is equal to
"1.0 times 10 to the power of 9".
3.14159265358979323846264338327950288419716939937510 // Pi
1.0e9 // A billion
1.0e-9 // A billionth4.1.3 string
A string can be seen as an array of values from 0 to 2³²-1.
Usually a string contains text such as a word, a sentence, a page or
even a whole book. But it can also contain parts of a binary file,
compressed data or other binary data. Strings in Pike are shared,
which means that identical strings share the same memory space. This
reduces memory usage very much for most applications and also speeds
up string comparisons. We have already seen how to write a constant
string:
As you can see, any sequence of characters within double quotes is a string.
The backslash character is used to escape characters that are not allowed or
impossible to type. As you can see, \t is the sequence to produce
a tab character, \\ is used when you want one backslash and
\" is used when you want a double quote (") to be a part
of the string instead of ending it.
Also, \XXX where XXX is an
octal number from 0 to 37777777777 or \xXX where XX
is 0 to ffffffff lets you write any character you want in the
string, even null characters. From version 0.6.105, you may also use
\dXXX where XXX is 0 to 2³²-1. If you write two constant
strings after each other, they will be concatenated into one string.
"hello world" // hello world
"he" "llo" // hello
"\116" // N (116 is the octal ASCII value for N)
"\t" // A tab character
"\n" // A newline character
"\r" // A carriage return character
"\b" // A backspace character
"\0" // A null character
"\"" // A double quote character
"\\" // A singe backslash
"\x4e" // N (4e is the hexadecimal ASCII value for N)
"\d78" // N (78 is the decimal ACII value for N)
"hello world\116\t\n\r\b\0\"\\" // All of the above
"\xff" // the character 255
"\xffff" // the character 65536
"\xffffff" // the character 16777215
"\116""3" // 'N' followed by a '3'
However, you should be aware that this does in fact create a new string and
it may need to copy the string s to do so. This means that the above
operation can be quite slow for large strings. You have been warned.
Most of the time, you can use replace, sscanf, `/
or some other high-level string operation to avoid having to use the above
construction too much.
string s="hello torld";
s[6]='w';
All the comparison operators plus the operators listed here can be used on strings:
Also, these functions operates on strings:
As you can see, each element in the array can contain any type of value. Indexing and ranges on arrays works just like on strings, except with arrays you can change values inside the array with the index operator. However, there is no way to change the size of the array, so if you want to append values to the end you still have to add it to another array which creates a new array. Figure 4.1 shows how the schematics of an array. As you can see, it is a very simple memory structure.({ }) // Empty array
({ 1 }) // Array containing one element of type int
({ "" }) // Array containing a string
({ "", 1, 3.0 }) // Array of three elements, each of different type

Operators and functions usable with arrays:

Each index-value pair is floating around freely inside the mapping. There is exactly one value for each index. We also have a (magical) lookup function. This lookup function can find any index in the mapping very quickly. Now, if the mapping is called m and we index it like this: m [ i ] the lookup function will quickly find the index i in the mapping and return the corresponding value. If the index is not found, zero is returned instead. If we on the other hand assign an index in the mapping the value will instead be overwritten with the new value. If the index is not found when assigning, a new index-value pair will be added to the mapping. Writing a constant mapping is easy:
([ ]) // Empty mapping
([ 1:2 ]) // Mapping with one index-value pair, the 1 is the index
([ "one":1, "two":2 ]) // Mapping which maps words to numbers
([ 1:({2.0}), "":([]), ]) // Mapping with lots of different types
As with arrays, mappings can contain any type. The main difference is that the index can be any type too. Also note that the index-value pairs in a mapping are not stored in a specific order. You can not refer to the fourteenth key-index pair, since there is no way of telling which one is the fourteenth. Because of this, you cannot use the range operator on mappings.
The following operators and functions are important:

Instead, the index operator will return 1 if the value was found in the multiset and 0 if it was not. When assigning an index to a multiset like this: mset[ ind ] = val the index ind will be added to the multiset mset if val is true. Otherwise ind will be removed from the multiset instead.
Writing a constant multiset is similar to writing an array:
Note that you can actually have more than one of the same index in a multiset. This is normally not used, but can be practical at times.(< >) // Empty multiset
(< 17 >) // Multiset with one index: 17
(< "", 1, 3.0, 1 >) // Multiset with 3 indices

You can also use the cast operator like this:program p = compile_file("hello_world.pike");
This will also load the program hello_world.pike, the only difference is that it will cache the result so that next time you do (program)"hello_world" you will receive the _same_ program. If you call compile_file("hello_world.pike") repeatedly you will get a new program each time.program p = (program) "hello_world";
There is also a way to write programs inside programs with the help of the class keyword:
The class keyword can be written as a separate entity outside of all functions, but it is also an expression which returns the program written between the brackets. The class_name is optional. If used you can later refer to that program by the name class_name. This is very similar to how classes are written in C++ and can be used in much the same way. It can also be used to create structs (or records if you program Pascal). Let's look at an example:class class_name {
inherits, variables and functions
}
This could be a small part of a better record register program. It is not a complete executable program in itself. In this example we create a program called record which has three identifiers. In add_empty_record a new object is created by calling record. This is called cloning and it allocates space to store the variables defined in the class record. Show_record takes one of the records created in add_empty_record and shows the contents of it. As you can see, the arrow operator is used to access the data allocated in add_empty_record. If you do not understand this section I suggest you go on and read the next section about objects and then come back and read this section again.class record {
string title;
string artist;
array(string) songs;
}
array(record) records = ({});
void add_empty_record()
{
records+=({ record() });
}
void show_record(record rec)
{
write("Record name: "+rec->title+"\n");
write("Artist: "+rec->artist+"\n");
write("Songs:\n");
foreach(rec->songs, string song)
write(" "+song+"\n");
}
compile_file simply reads the file given as argument, compiles it and returns the resulting program. compile_string instead compiles whatever is in the string p. The second argument, filename, is only used in debug printouts when an error occurs in the newly made program. Both compile_file and compile_string calls compile to actually compile the string after calling cpp on it.program compile(string p);
program compile_file(string filename);
program compile_string(string p, string filename);
The following operators and functions are important:

Here we can clearly see how the function show prints the contents of the variables in that object. In essence, instead of accessing the data in the object with the -> operator, we call a function in the object and have it write the information itself. This type of programming is very flexible, since we can later change how record stores its data, but we do not have to change anything outside of the record program.class record {
string title;
string artist;
array(string) songs;
void show()
{
write("Record name: "+title+"\n");
write("Artist: "+artist+"\n");
write("Songs:\n");
foreach(songs, string song)
write(" "+song+"\n");
}
}
array(record) records = ({});
void add_empty_record()
{
records+=({ record() });
}
void show_record(object rec)
{
rec->show();
}
Functions and operators relevant to objects:

In this example, the function bar returns a pointer to the function foo. No indexing is necessary since the function foo is located in the same object. The function gazonk simply calls foo. However, note that the word foo in that function is an expression returning a function pointer that is then called. To further illustrate this, foo has been replaced by bar() in the function teleledningsanka.int foo() { return 1; }
function bar() { return foo; }
int gazonk() { return foo(); }
int teleledningsanka() { return bar()(); }
For convenience, there is also a simple way to write a function inside another function. To do this you use the lambda keyword. The syntax is the same as for a normal function, except you write lambda instead of the function name:
The major difference is that this is an expression that can be used inside an other function. Example:lambda ( types ) { statements }
This is the same as the first two lines in the previous example, the keyword lambda allows you to write the function inside bar.function bar() { return lambda() { return 1; }; )
Note that unlike C++ and Java you can not use function overloading in Pike. This means that you cannot have one function called 'foo' which takes an integer argument and another function 'foo' which takes a float argument.
This is what you can do with a function pointer.
This program will of course write Hello world.int main(int argc, array(string) argv)
{
array(string) tmp;
tmp=argv;
argv[0]="Hello world.\n";
write(tmp[0]);
}
Sometimes you want to create a copy of a mapping, array or object. To do so you simply call copy_value with whatever you want to copy as argument. Copy_value is recursive, which means that if you have an array containing arrays, copies will be made of all those arrays.
If you don't want to copy recursively, or you know you don't have to copy recursively, you can use the plus operator instead. For instance, to create a copy of an array you simply add an empty array to it, like this: copy_of_arr = arr + ({}); If you need to copy a mapping you use an empty mapping, and for a multiset you use an empty multiset.
As you can see there are some interesting ways to specify types. Here is a list of what is possible:int x; // x is an integer
int|string x; // x is a string or an integer
array(string) x; // x is an array of strings
array x; // x is an array of mixed
mixed x; // x can be any type
string *x; // x is an array of strings
// x is a mapping from int to string
mapping(string:int) x;
// x implements Stdio.File
Stdio.File x;
// x implements Stdio.File
object(Stdio.File) x;
// x is a function that takes two integer
// arguments and returns a string
function(int,int:string) x;
// x is a function taking any amount of
// integer arguments and returns nothing.
function(int...:void) x;
// x is ... complicated
mapping(string:function(string|int...:mapping(string:array(string)))) x;
|
The third column, "Identifier" is the name of the function that actually evaluates the operation. For instance, a + b can also be written as `+(a, b). I will show you how useful this can be at the end of this chapter.
When applied to integers or floats these operators do exactly what they are supposed to do. The only operator in the list not known from basic math is the modulo operator. The modulo operator returns the remainder from an integer division. It is the same as calculating a - floor(a / b) * b. floor rounds the value down to closest lower integer value. Note that the call to floor isn't needed when operating on integers, since dividing two integers will return the result as an integer and it is always rounded down. For instance, 8 / 3 would return 2.
If all arguments to the operator are integers, the result will also be an integer. If one is a float and the other is an integer, the result will be a float. If both arguments are float, the result will of course be a float.
However, there are more types in Pike than integers and floats. Here is the complete list of combinations of types you can use with these operators:
|
|
The other operators in the table above can only be used with integers, floats and strings. If you compare an integer with a float, the int will be promoted to a float before the comparison. When comparing strings, lexical order is used and the values of the environment variables LC_CTYPE and LC_LANG are respected.
|
|
When intersection, union or symmetric difference is used on an array each element in the array is considered by itself. So intersecting two arrays will result in an array with all elements that are present in both arrays. Example: ({7,6,4,3,2,1}) & ({1, 23, 5, 4, 7}) will return ({7,4,1}). The order of the elements in the returned array will always be taken from the left array. Elements in multisets are treated the same as elements in arrays. When doing a set operation on a mapping however, only the indices are considered. The values are just copied with the indices. If a particular index is present in both the right and left argument to a set operator, the one from the right side will be used. Example: ([1:2]) | ([1:3]) will return ([1:3]).
|
|
When indexing an array or string it is sometimes convenient to access index from the end instead of from the beginning. This function can be performed by using a negative index. Thus arr[-i] is the same as arr[sizeof(arr)-i]. Note however that this behavior does not apply to the range operator. Instead the range operator clamps it's arguments to a suitable range. This means that a[b..c] will be treated as follows:
The variable can be a local variable, a global variable or an index in an array, object, multiset or mapping. This will of course set the value stored in variable to expression. Note that the above is also an expression which returns the value of the expression. This can be used in some interesting ways:variable = expression;
Using assignments like this can however be confusing to novice users, or users who come from a Pascal or Basic background. Especially the if statement can be mistaken for if(variable == expression) which would mean something completely different. As I mentioned earlier, the assignment operator can be combined with another operator to form operators that modify the contents of a variable instead of just assigning it. Here is a list of all the combinations:variable1 = variable2 = 1; // Assign 1 to both variables
variable1 =(variable2 = 1); // Same as above
// Write the value of the expression, if any
if(variable = expression)
write(variable);
|
|
object clone(mixed p, mixed ... args) { ( (program)p )(@args); }
On the subject of function calls, the splice operator should also be mentioned. The splice operator is an at sign in front of an expression. The expression should always be an array. The splice operator sends each of the elements in the array as a separate argument to the function call. The splice operator can only be used in an argument list for a function call.
Then there are the increment and decrement operators. The increment and decrement operators are somewhat limited: they can only be used on integers. They provide a short and fast way to add or subtract one to an integer. If the operator is written before the variable (++a) the returned value will be what the variable is after the operator has added/subtracted one to it. If the operator is after the variable (a++) it will instead return the value of the variable before it was incremented/decremented.
Last, and in some respect least, is the comma operator. It doesn't do much. In fact, it simply evaluates the two arguments and then returns the right hand one. This operator is mostly useful to produce smaller code, or to make defines that can be used in expressions.
Examples:
|
This analogy has one major flaw, when running programs in UNIX they actually run simultaneously. UNIX is multitasking, Pike is not. When one object is executing code, all the other objects has to wait until they are called. An exception is if you are using threads as will be discussed in a later chapter.
In my experience, the advantages of object oriented programming are:
Similarly, if you want to load another file and call functions in it, you can do it with compile_file(), or you can use the cast operator and cast the filename to a string. You can also use the module system, which we will discuss further in the next chapter.program scriptclass=compile_file(argv[0]); // Load script
object script=scriptclass(); // clone script
int ret=script->main(sizeof(argv), argv); // call main()
If you don't want to put each program in a separate file, you can use the class keyword to write all your classes in one file. We have already seen an example how this in chapter 4 "Data types", but let's go over it in more detail. The syntax looks like this:
This construction can be used almost anywhere within a normal program. It can be used outside all functions, but it can also be used as an expression in which case the defined class will be returned. In this case you may also leave out the class_name and leave the class unnamed. The class definition is simply the functions and programs you want to add to the class.class class_name {
class_definition
}
To make it easier to program, defining a class is also to define a constant with that name. Essentially, these two lines of code do the same thing:
Because classes are defined as constants, it is possible to use a class defined inside classes you define later, like this:class foo {};
constant foo = class {};
class foo
{
int test() { return 17; }
};
class bar
{
program test2() { return foo; }
};
What inherit does is that it copies all the variables and functions from the inherited program into the current one. You can then re-define any function or variable you want, and you can call the original one by using a :: in front of the function name. The argument to inherit can be one of the following:inherit "hello_world";
int main(int argc, array(string) argv)
{
write("Hello world version 1.0\n");
return ::main(argc,argv);
}
Let's look at an example. We'll split up an earlier example into three parts and let each inherit the previous part. It would look something like this:

As you can see it would be impossible to separate the different read and main functions without using inherit names. If you tried calling just read without any :: or inherit name in front of it Pike will call the last read defined, in this case it will call read in the fourth inherit.inherit Stdio.File; // This inherit is named File
inherit Stdio.FILE; // This inherit is named FILE
inherit "hello_word"; // This inherit is named hello_world
inherit Stdio.File : test1; // This inherit is named test1
inherit "hello_world" : test2; // This inherit is named test2
void test()
{
File::read(); // Read data from the first inherit
FILE::read(); // Read data from the second inherit
hello_world::main(0,({})); // Call main in the third inherit
test1::read(); // Read data from the fourth inherit
test2::main(0,({})); // Call main in the fifth inherit
::read(); // Read data from all inherits
}
If you leave the inherit name blank and just call ::read Pike will call all inherited read() functions. If there is more than one inherited read function the results will be returned in an array.
Let's look at another example:
This short piece of code works a lot like the UNIX command cat. It reads all the files given on the command line and writes them to stdout. As an example, I have inherited Stdio.File twice to show you that both files are usable from my program.#!/usr/local/bin/pike
inherit Stdio.File : input;
inherit Stdio.File : output;
int main(int argc, array(string) argv)
{
output::create("stdout");
for(int e=1;e<sizeof(argv);e++)
{
input::open(argv[e],"r");
while(output::write(input::read(4096)) == 4096);
}
}
The following table assumes that a and b are objects and shows what will be evaluated if you use that particular operation on an object. Note that some of these operators, notably == and ! have default behavior which will be used if the corresponding method is not defined in the object. Other operators will simply fail if called with objects. Refer to chapter 5 "Operators" for information on which operators can operate on objects without operator overloading.
|
Here is a really silly example of a program that will write 10 to stdout when executed.
It is important to know that some optimizations are still performed even when operator overloading is in effect. If you define a multiplication operator and multiply your object with one, you should not be surprised if the multiplication operator is never called. This might not always be what you expect, in which case you are better off not using operator overloading.#!/usr/local/bin/pike
class three {
int `+(int foo) { return 3+foo; }
};
int main()
{
write(sprintf("%d\n",three()+7));
}
void find_song(string title) |
The string str will be matched against the format string fmt. fmt can contain strings separated by %d,%s,%c and %f. Every % corresponds to one lvalue. An lvalue is the name of a variable, a name of a local variable, an index in an array, mapping or object. It is because of these lvalues that sscanf can not be implemented as a normal function.int sscanf(string str, string fmt, lvalue ...)
Whenever a percent is found in the format string, a match is according to the following table:
| %b | reads a binary integer |
| %d | reads a decimal integer |
| %o | reads an octal integer |
| %x | reads a hexadecimal integer |
| %D | reads an integer that is either octal (leading zero), hexadecimal (leading 0x) or decimal. |
| %f | reads a float |
| %c | matches one char and returns it as an integer |
| %2c | matches two chars and returns them as an integer (short) |
| %4F | matches four chars and returns them as a float (IEEE single precision) |
| %8F | matches eigth chars and returns them as a float (IEEE double precision) |
| %s | reads a string. If followed by %d, %s will read any non-numerical characters. If followed by a %[], %s will read any characters not present in the set. If followed by normal text, %s will match all characters up to but not including the first occurrence of that text. |
| %5s | gives a string of 5 characters (5 can be any number) |
| %[set] | matches a string containing a given set of characters (those given inside the brackets). %[^set] means any character except those inside brackets. Example: %[0-9H] means any number or 'H'. |
| %{format%} | Repeatedly matches 'format' as many times as possible and assigns an array of arrays with the results to the lvalue. |
If a * is put between the percent and the operator, the operator will only match its argument, not assign any variables.
Sscanf does not use backtracking. Sscanf simply looks at the format string up to the next % and tries to match that with the string. It then proceeds to look at the next part. If a part does not match, sscanf immediately returns how many % were matched. If this happens, the lvalues for % that were not matched will not be changed.
Let's look at a couple of examples:
// a will be assigned "oo" and 1 will be returned
sscanf("foo","f%s",a);
// a will be 4711 and b will be "bar", 2 will be returned
sscanf("4711bar","%d%s",a,b);
// a will become "test"
sscanf(" \t test","%*[ \t]%s",a)
// Remove "the " from the beginning of a string
// If 'str' does not begin with "the " it will not be changed
sscanf(str,"the %s",str);
SEE ALSO : sprintf
If an error occurs, catch will return a description of the error. The description of the error has the following format:catch { statements }
If no error occurs, catch will return zero. You may emulate your own errors using the function throw, described in chapter 16 "Builtin functions".({
"error description",
backtrace()
})
Example:
int x,y;
// This might generate "division by zero"
mixed error=catch { x/=y; };
However, gauge simply returns how many seconds the code took to execute. This can be used to find out how fast your code actually is.. :) Only CPU time used by the Pike process is measured. This means that if it takes two seconds to execute but only uses 50 % CPU, this function will return 1.0.gauge { statements }
This will return the string "void" since exit is a function that returns void. It will not execute the function exit and exit the process as you might expect.typeof( exit(1) )
If you want to know the type after evaluation, use sprintf("%t", expr).
here is a list of the basic Pike modules:
* These modules might not be available depending on how Pike was compiled
and whether support for these functions exist on your system.
When you use Stdio Pike will look for that module:
You can also use the . operator without an identifier preceeding it to
access modules in the same directory as the program itself. For instance,
.my_module.foo would mean 'the identifier foo in the module
my_module in this directory.
8.1 How to use modules
A module is a bunch of functions, programs or other modules collected in
one symbol. For instance, the module Stdio contains the objects
stdin, stdout and stderr. To access these objects
you can write Stdio.stdin, Stdio.stdout or
Stdio.stderr anywhere in your program where an object of that type
is acceptable. If you use Stdio a lot you can put
import Stdio; in the beginning of your program. This will import
all the identifiers from the module Stdio into your program, making it
possible to write just stdin instead of Stdio.stdin.
It is also possible to import all modules in a directory with import
by putting the directory name in doublequtes. So, to import all modules in
the current directory, you would use import ".";.
8.2 Where do modules come from?
Modules are not loaded until you use them, which saves memory unless you use
all the modules. However, if you want to write your own modules it is important
to know how modules are located and loaded.
For each of these directories, Pike will do the following:
As you can see, quite a lot of work goes into finding the modules, this
makes it possible to choose the most convenient way to build your own Pike
modules.
8.3 The . operator
The period operator is not really an operator, as it is always evaluated
during the compilation. It works similarly to the index and arrow operators,
but can only be used on constant values such as modules. In most cases,
modules are simply a clone of a program, in which case the identifiers in
the module will be the same as those in the program. But some modules,
like those created from directories, overload the index operator so that
the identifiers in the module can be something other than those in the program.
For directory modules, the index operator looks in the directory it was
cloned for to find the identifiers.
8.4 How to write a module
Here is an example of a simple module:
if we save this short file as Trig.pmod we can now use this
module like this:
constant PI = 3.14159265358979323846264338327950;
float cos2(float f) { return pow(cos(f),2.0); }
or like this:
int main()
{
write(sprintf("%f\n",.Trig.cos2(.Trig.PI));
}import .Trig;
int main()
{
write(sprintf("%f\n",cos2(PI));
}8.5 Simple exercises
|
|
However, instead of cloning and then calling open(), you can clone the File with a filename and open mode. This is the same thing as cloning and then calling open, except shorter and faster. Default open mode is "r" and default mask is 0666.
Alternatively, you can clone a File with "stdin", "stdout" or "stderr" as argument. This will open the specified standard stream.
For the advanced users, you can use the file descriptors of the systems (note: emulated by pike on some systems - like NT). This is only useful for streaming purposes on unix systems. This is not recommended at all if you don't know what you're into. Default mode for this is "rw".
| 'r' | open file for reading |
| 'w' | open file for writing |
| 'a' | open file for append (use with 'w') |
| 't' | truncate file at open (use with 'w') |
| 'c' | create file if it doesn't exist (use with 'w') |
| 'x' | fail if file already exist (use with 'c') |
How should _always_ contain at least one of 'r' or 'w'.
The third argument is protection bits if the file is created. Default is 0666 (all read+write, in octal notation).
If a one is given as second argument to read(), read will not try its best to read as many bytes as you asked it to read, it will merely try to read as many bytes as the system read function will return. This mainly useful with stream devices which can return exactly one row or packet at a time.
If no arguments are given, read will read to the end of the file/stream.
If a one is given as a second argument to read_oob(), only as many bytes of out-of-band data as are currently available will be returned.
If no arguments are given, read_oob will read to the end of the stream.
It is not guaranteed that all out-of-band data sent from the other end will be received. Most streams only allow for a single byte of out-of-band data at a time.
It is not guaranteed that all out-of-band data will be received at the other end. Most streams only allow for a single byte of out-of-band data at a time. Some streams will send the rest of the data as ordinary data.
When out-of-band data arrives on the stream, read_oob_callback will be called with some or all of this data. When the stream allows out-of-band data to be sent, write_oob_callback is called so that you can write out-of-band data to it.
All callbacks will have the id of file as first argument when called.
If no arguments are given, the callbacks are not changed. The stream is just set to nonblocking mode.
/* Redirect stdin to come from the file 'foo' */
object o=Stdio.File();
o->open("foo","r");
o->dup2(Stdio.File("stdin"));
If you give a port number to this function, the socket will be bound to this port locally before connecting anywhere. This is only useful for some silly protocols like FTP. You may also specify an address to bind to if your machine has many IP numbers.
This function returns 1 for success, 0 otherwise. Note that if the socket is in nonblocking mode, you have to wait for a write or close callback before you know if the connection failed or not.
Here is an example of how to use the TCP functions in Stdio.File in blocking mode. This short program takes a URL as first argument, connects to the WWW server, sends a HEAD request and writes the reply to stdout. For clarity, all calls to Stdio.File use File:: even if that is not strictly necessary.
import Stdio;
inherit File;
int main(int argc, array(string) argv)
{
string host;
string path="";
int port=80;
sscanf(argv[1],"http://%s",argv[1]);
sscanf(argv[1],"%s/%s",host,path);
sscanf(host,"%s:%d",host,port);
if(!File::open_socket())
{
perror("Open socket failed");
exit(1);
}
if(!File::connect(host,port))
{
perror("Failed to connect to remote host");
exit(1);
}
File::write(sprintf("HEAD /%s HTTP/1.0\n",path));
stdout::write(File::read());
}
Example:
This example will read lines from standard input for as long as there are more lines to read. Each line will then be written to stdout together with the line number. We could use Stdio.stdout.write instead of just write because they are the same function.int main()
{
int line;
while(string s=Stdio.stdin.gets())
write(sprintf("%5d: %s\n",line++,s));
}
If the optional argument IP is given, bind will try to bind to this IP name (or number).
([ "data" : string recieved data "ip" : string recieved from this ip "port" : int ...and this port ])
([ "data" : string recieved data "ip" : string recieved from this ip "port" : int ...and this port ])
Lookup of terminal information will first be done in the systems terminfo database, and if that fails in the termcap database. If neither database exists, a hardcoded entry for "dumb" will be used.
infd defaults to Stdio.stdout.
interm defaults to Stdio.Terminfo.getTerm().
outfd defaults to infd, unless infd is 0, in which case outfd defaults to Stdio.stdout.
outterm defaults to interm.
Any of headers, from and trailers may be left out by setting them to 0.
Setting offset to -1 means send from the current position in from.
Setting len to -1 means send until from's end of file is reached.
For callback to be called, the backend must be active (ie main() must have returned -1).
In some cases, the backend must also be active for any sending to be performed at all.
This function filters the indata through the UNIX-command /bin/grep and return the result.string grep(string indata, string needle)
{
object out=Stdio.File(),in=Stdio.File();
object process=
Process.create_process(({"/bin/grep",needle}),
(["stdin":out->pipe(),
"stdout":in->pipe()]) );
out->write(indata);
out->close();
process->wait();
return in->read();
}
Usually WWW involves HTML. HTML (Hyper-Text Markup Language) is a way to write documents with embedded pictures and links to other pages. These links are normally displayed underlined and if you click them your WWW- browser will load whatever document that link leads to.
We inherit Stdio.Port into this program so we can bind a TCP socket to accept incoming connection. A socket is simply a number to separate communications to and from different programs on the same computer.#!/usr/local/bin/pike
/* A very small httpd capable of fetching files only. * Written by Fredrik Hübinette as a demonstration of Pike. */
inherit Stdio.Port;
Next are some constants that will affect how uHTTPD will operate. This uses the preprocessor directive #define. The preprocessor is the first stage in the compiling process and can make textual processing of the code before it is compiled. As an example, after the first define below, all occurrences of 'BLOCK' will be replaced with 16060.
A port is a destination for a TCP connection. It is simply a number on the local computer. 1905 is not the standard port for HTTP connections though, which means that if you want to access this WWW server from a browser you need to specify the port like this: http://my.host.my.domain:1905//* Amount of data moved in one operation */
#define BLOCK 16060
/* Where do we have the html files ? */
#define BASE "/usr/local/html/"
/* File to return when we can't find the file requested */
#define NOFILE "/user/local/html/nofile.html"
/* Port to open */
#define PORT 1905
Next we declare a class called output_class. Later we will clone one instance of this class for each incoming HTTP connection.
Our new class inherits Stdio.File twice. To be able to separate them they are then named 'socket' and 'file'.class output_class
{
inherit Stdio.File : socket;
inherit Stdio.File : file;
Then there is a global variable called offset which is initialized to zero. (Each instance of this class will have its own instance of this variable, so it is not truly global, but...) Note that the initialization is done when the class is cloned (or instantiated if you prefer C++ terminology).int offset=0;
Next we define the function write_callback(). Later the program will go into a 'waiting' state, until something is received to process, or until there is buffer space available to write output to. When that happens a callback will be called to do this. The write_callback() is called when there is buffer space available. In the following lines 'void' means that it does not return a value. Write callback will be used further down as a callback and will be called whenever there is room in the socket output buffer.
The following line means: call seek in the inherited program 'file'.void write_callback()
{
int written;
string data;
Move the file pointer to the where we want to the position we want to read from. The file pointer is simply a location in the file, usually it is where the last read() ended and the next will begin. seek() can move this pointer to where we want it though.file::seek(offset);
Read BLOCK (16060) bytes from the file. If there are less that that left to read only that many bytes will be returned.data=file::read(BLOCK);
If we managed to read something...if(strlen(data))
{
... we try to write it to the socket.written=socket::write(data);
Update offset if we managed to write to the socket without errors.if(written >= 0)
{
offset+=written;
return;
}
If something went wrong during writing, or there was nothing left to read we destruct this instance of this class.werror("Error: "+socket::errno()+".\n");
}
That was the end of write_callback()destruct(this_object());
}
Next we need a variable to buffer the input received in. We initialize it to an empty string.
And then we define the function that will be called when there is something in the socket input buffer. The first argument 'id' is declared as mixed, which means that it can contain any type of value. The second argument is the contents of the input buffer.string input="";
Append data to the string input. Then we check if we have received a a complete line yet. If so we parse this and start outputting the file.void read_callback(mixed id,string data)
{
string cmd;
input+=data;
This sscanf is pretty complicated, but in essence it means: put the first word in 'input' in 'cmd' and the second in 'input' and return 2 if successful, 0 otherwise.if(sscanf(input,"%s %s%*[\012\015 \t]",cmd,input)>2)
{
If the first word isn't GET print an error message and terminate this instance of the program. (and thus the connection)if(cmd!="GET")
{
werror("Only method GET is supported.\n");
destruct(this_object());
return;
}
Remove the leading slash.sscanf(input,"%*[/]%s",input);
Combine the requested file with the base of the HTML tree, this gives us a full filename beginning with a slash. The HTML tree is the directory on the server in which the HTML files are located. Normally all files in this directory can be accessed by anybody by using a WWW browser. So if a user requests 'index.html' then that file name is first added to BASE (/home/hubbe/www/html/ in this case) and if that file exists it will be returned to the browser.input=BASE+combine_path("/",input);
Try opening the file in read-only mode. If this fails, try opening NOFILE instead. Opening the file will enable us to read it later.if(!file::open(input,"r"))
{
If this fails too. Write an error message and destruct this object.if(!file::open(NOFILE,"r"))
{
Ok, now we set up the socket so we can write the data back.werror("Couldn't find default file.\n");
destruct(this_object());
return;
}
}
Set the buffer size to 64 kilobytes.socket::set_buffer(65536,"w");
Make it so that write_callback is called when it is time to write more data to the socket.socket::set_nonblocking(0,write_callback,0);
Jump-start the writing.write_callback();
That was the end of read_callback().}
}
This function is called if the connection is closed while we are reading from the socket.
This function is called when the program is instantiated. It is used to set up data the way we want it. Extra arguments to clone() will be sent to this function. In this case it is the object representing the new connection.void selfdestruct() { destruct(this_object()); }
We insert the data from the file f into 'socket'.void create(object f)
{
socket::assign(f);
Then we set up the callback functions and sets the file nonblocking. Nonblocking mode means that read() and write() will rather return that wait for I/O to finish. Then we sit back and wait for read_callback to be called.socket::set_nonblocking(read_callback,0,selfdestruct);
End of create()}
End of the new class.};
Next we define the function called when someone connects.
This creates a local variable of type 'object'. An object variable can contain a clone of any program. Pike does not consider clones of different programs different types. This also means that function calls to objects have to be resolved at run time.void accept_callback()
{
object tmp_output;
The function accept clones a Stdio.File and makes this equal to the newly connected socket.tmp_output=accept();
If it failed we just return.if(!tmp_output) return;
Otherwise we clone an instance of 'output_class' and let it take care of the connection. Each clone of output_class will have its own set of global variables, which will enable many connections to be active at the same time without data being mixed up. Note that the programs will not actually run simultaneously though.output_class(tmp_output);
Destruct the object returned by accept(), output_class has already copied the contents of this object.destruct(tmp_output);
Then there is main, the function that gets it all started.}
Write an encouraging message to stderr.int main(int argc, array(string) argv)
{
werror("Starting minimal httpd\n");
Bind PORT and set it up to call accept_callback as soon as someone connects to it. If the bind() fails we write an error message and return the 17 to indicate failure.if(!bind(PORT, accept_callback))
{
werror("Failed to open socket (already bound?)\n");
return 17;
}
If everything went ok, we return -17, any negative value returned by main() means that the program WON'T exit, it will hang around waiting for events instead. (like someone connecting)return - 17; /* Keep going */
That's it, this simple program can be used as the basis for a simple WWW-server. Note that today most WWW servers are very complicated programs, and the above program can never replace a modern WWW server. However, it is very fast if you only want a couple of web pages and have a slow machine available for the server.}
In POSIX threads, mutex locks can only be unlocked by the same thread
that locked them. In Pike any thread can unlock a locked mutex.
10.1 Starting a thread
Starting a thread is very easy. You simply call thread_create with a function
pointer and any arguments it needs and that function will be executed in a
separate thread. The function thread_create will return immediately and both
the calling function and the called function will execute at the same time. Example:
This may all seem very simple, but there are a few complications to
watch out for:
void foo(int x)
{
for(int e=0;e<5;e++)
{
sleep(1);
write("Hello from thread "+x+".\n");
}
}
int main()
{
thread_create(foo, 2);
thread_create(foo, 3);
foo(1);
}
This is quite harmless as long as it is only used from one thread at a time,
but if two threads call it it at the same time, there is a slight chance that
both threads will discover that map[i] is zero and both threads will
then do map[i]=({j}); and one value of j will be lost.
This type of bug can be extremely hard to debug.
The above problem can be solved with the help of Mutexes and Condition
variables. Mutexes are basically a way to keep other threads out while a task
is being performed. Conditions, or condition variables, are used to inform
other threads that they don't have to wait any longer. Pike also provides
two different kinds of pipelines to send data from one thread to another, which
makes it very simple to write threaded programs. Let's look at an example:
void mapadd(mapping m, int i, int j)
{
if(map[i])
map[i]+=({j});
else
map[i]=({j});
}
This is an example of a simple grep-like program. It looks for the string
given as first argument to the program in the files given as the rest
of the arguments. Don't worry if you do not understand it yet. Read the
descriptions of the functions and classes below and come back and read
this example again.
#!/usr/local/bin/pike
import Thread; // We need fifos
inherit Fifo; // Fifo used to supply workers
inherit Fifo : ended; // Fifo used to wait for workers
void worker(string lookfor)
{
while(string file=Fifo::read())
{
int linenum=1;
object o=Stdio.FILE(file,"r");
while(string line=o->gets())
{
if(search(line, lookfor) >=0)
write(sprintf("%s:%d: %s\n",file, linenum, line));
linenum++;
}
}
ended::write(0);
}
int main(int argc, array(string) argv)
{
for(int e=0;e<4;e++) // Start workers
thread_create(worker,argv[e]);
for(int e=2;e<argc;e++) // Feed workers
Fifo::write(argv[1]);
for(int e=0;e<4;e++) // Tell workers to die
Fifo::write(0);
for(int e=0;e<4;e++) // Wait for workers to die
ended::read();
exit(0);
}10.2 Threads reference section
This section describes all thread-related functions and classes.
/* This simple program can be used to exchange data between two
* programs. It is similar to Thread.Fifo, but can only hold one
* element of data.
*/
inherit Thread.Mutex : r_mutex;
inherit Thread.Mutex : w_mutex;
object r_lock=r_mutex::lock();
object w_lock;
mixed storage;
void write(mixed data)
{
w_lock=w_mutex::lock();
storage=data;
destruct(r_lock);
}
mixed read()
{
mixed tmp;
r_lock=r_mutex::lock();
tmp=storage;
storage=0;
destruct(w_lock);
return tmp;
}// This program implements a fifo that can be used to send
// data between two threads.
inherit Thread.Condition : r_cond;
inherit Thread.Condition: w_cond;
inherit Thread.Mutex: lock;
array buffer = allocate(128);
int r_ptr, w_ptr;
int query_messages() { return w_ptr - r_ptr; }
// This function reads one mixed value from the fifo.
// If no values are available it blocks until a write has been done.
mixed read()
{
mixed tmp;
// We use this mutex lock to make sure no write() is executed
// between the query_messages and the wait() call. If it did
// we would wind up in a deadlock.
object key=lock::lock();
while(!query_messages()) r_cond::wait(key);
tmp=buffer[r_ptr++ % sizeof(buffer)];
w_cond::signal();
return tmp;
}
// This function pushes one mixed value on the fifo.
// If the fifo is full it blocks until a value has been read.
void write(mixed v)
{
object key=lock::lock();
while(query_messages() == sizeof(buffer)) w_cond::wait(key);
buffer[w_ptr++ % sizeof(buffer)]=v;
r_cond::signal();
}
void wait(object mutex_key);
object(Thread.Fifo) Thread.Fifo();
object(Thread.Fifo) Thread.Fifo(int size);
#!/usr/local/bin/pike
/* A very small threaded httpd capable of fetching files only. * Written by Fredrik Hübinette as a demonstration of Pike */
import Thread;
inherit Stdio.Port;
/* number of bytes to read for each write */
#define BLOCK 16384
/* Where do we have the html files ? */
#define BASE "/home/hubbe/pike/src/"
/* File to return when we can't find the file requested */
#define NOFILE "/home/hubbe/www/html/nofile.html"
/* Port to open */
#define PORT 1905
/* Number of threads to start */
#define THREADS 5
// There will be one of these for each thread
class worker
{
inherit Stdio.FILE : socket; // For communication with the browser
inherit Stdio.File : file; // For reading the file from disc
void create(function accept)
{
string cmd, input, tmp;
while(1)
{
socket::close(); // Close previous connection
file::close();
object o=accept(); // Accept a connection
if(!o) continue;
socket::assign(o);
destruct(o);
// Read request
sscanf(socket::gets(),"%s %s%*[\012\015 \t]",cmd, input);
if(cmd!="GET")
{
werror("Only method GET is supported.\n");
continue;
}
// Open the requested file
sscanf(input,"%*[/]%s",input);
input=BASE+combine_path("/",input);
if(!file::open(input,"r"))
{
if(!file::open(NOFILE,"r"))
{
werror("Couldn't find default file.\n");
continue;
}
}
// Copy data to socket
while(socket::write(file::read(BLOCK))==BLOCK);
}
}
};
int main(int argc, array(string) argv)
{
werror("Starting minimal threaded httpd\n");
// Bind the port, don't set it nonblocking
if(!bind(PORT))
{
werror("Failed to open socket (already bound?)\n");
return 17;
}
// Start worker threads
for(int e=1;e<THREADS;e++) thread_create(worker,accept);
worker(accept);
}
As stated in the beginning of this chapter; Pike threads are only available on some UNIX systems. The above example does not work if your system does not have threads.
> Array.diff("Hello world!"/"","Help!"/"");
Result: ({ /* 2 elements */
({ /* 3 elements */
({ /* 3 elements */
"H",
"e",
"l"
}),
({ /* 8 elements */
"l",
"o",
" ",
"w",
"o",
"r",
"l",
"d"
}),
({ /* 1 elements */
"!"
})
}),
({ /* 3 elements */
({ /* 3 elements */
"H",
"e",
"l"
}),
({ /* 1 elements */
"p"
}),
({ /* 1 elements */
"!"
})
})
})
> Array.diff_compare_table("Hello world!"/"","Help!"/"");
Result: ({ /* 12 elements */
({ /* 1 elements */
0
}),
({ /* 1 elements */
1
}),
({ /* 1 elements */
2
}),
({ /* 1 elements */
2
}),
({ }),
({ }),
({ }),
({ }),
({ }),
({ /* 1 elements */
2
}),
({ }),
({ /* 1 elements */
4
})
})
> Array.diff_longest_sequence("Hello world!"/"","Help!"/"");
Result: ({ /* 4 elements */
0,
1,
2,
4
})
Result: ({ /* 3 elements */
"2",
"4",
"6"
})
Second syntax:
Filter array calls fun in all the objects in the array arr, and
return all objects that returned true.
Third syntax:
Filter array calls all function pointers in the array arr, and
return all that returned true.
> array a = ({ 1,2,3,4,2,3,5,6 });
Result: ({ /* 8 elements */
1,
2,
3,
4,
2,
3,
5,
6
})
> array seq = Array.longest_ordered_sequence(a);
Result: ({ /* 6 elements */
0,
1,
4,
5,
6,
7
})
> rows(a,seq);
Result: ({ /* 6 elements */
1,
2,
2,
3,
5,
6
})
Second syntax:
Map array calls function fun in all objects in the array arr.
i.e. arr[x]=arr[x]->fun(@ args);
Third syntax:
Map array calls the functions in the array arr:
arr[x]=arr[x]->fun(@ args);
Array.reduce(aggregate, indices(allocate(4))) ->
({ ({ ({ 0, 1 }), 2 }), 3 })
Array.reduce(`+, ({}), "FOO?") ->
"FOO?"
Array.reduce(lambda(int a, int b) {
while(b) { int t=b; b=a%b; a=t; }
return a;
}, ({7191,21573,64719,33694}))
-> 17
Array.rreduce(aggregate, indices(allocate(4))) ->
({ 0, ({ 1, ({ 2, 3 }) }) })
Result: ({ /* 9 elements */
1,
"1",
"a",
2,
"2",
"b",
3,
"3",
"c"
})
> Array.splice(({1,2,3}),({"1","2","3"}),({"a","b"}));
Result: ({ /* 9 elements */
1,
"1",
"a",
2,
"2",
"b",
})
array sum_arrays(function fun,array arr1,...)
{
int e;
array res=allocate(sizeof(arr1));
for(e=0;e<sizeof(arr1);e++)
{
res[e]=fun(arr1[e],arr2[e],...);
}
return res;
}
Simple ehh?
All images handled by this module are stored as 24-bit RGB images. This means that a 1024 pixel wide and 1024 pixel high image will use 1024*1024*3 bytes = 3 megabytes. It is quite easy to mess up and use up all the memory by giving the wrong argument to one of the scaling functions.
Most functions in this module work by creating a new Image and then returning that instead of changing the Image you are working with. This makes it possible to share the same image between many variables without having to worry that it will be changed by accident. This can reduce the amount of memory used.
Many functions in this module work with the 'current color', this can be thought of as the background color if you wish. To change the current color you use 'setcolor'.
Let's look at an example of how this can be used:
This very simple example can be used as a CGI script to produce a gif image which says what time it is in white text on a black background.#!/usr/local/bin/pike
int main()
{
write("Content-type: image/gif\n\n");
object font=Image.font();
font->load("testfont");
object image=font->write(ctime(time));
write(Image.GIF.encode(image));
}
| Image.Image | Basic image manipulation |
| Image.Font | Creating images from text |
| Image.Colortable | Color reduction, quantisation and dither |
| Image.Color | Color names, objects and conversion |
| encoding/decoding image files | |
| Image.GIF | GIF encoding/decoding capabilities |
| Image.JPEG | JPEG encoding/decoding capabilities (needs libjpeg) |
| Image.PNG | PNG encoding/decoding capabilities (needs libz) |
| Image.PNM | PNM (PBM/PGM/PPM) encoding/decoding capabilities |
| Image.XFace | XFace encoding/decoding capabilities (needs libgmp) |
| Image.XWD | XWD (X-windows dump) decoding capabilities |
| advanced | |
| Image.X | module for supporting X-windows low-level formats, keeping a lot of bit-mending stuff. |
Image module documentation is based on these file versions:$Id: blit.c,v 1.37 1999/06/18 19:19:14 mirar Exp $ $Id: blit_layer_include.h,v 1.6 1999/04/13 12:32:11 mirar Exp $ $Id: colors.c,v 1.30 2000/02/03 19:02:22 grubba Exp $ $Id: colortable.c,v 1.77 1999/10/30 11:51:41 mirar Exp $ $Id: colortable.h,v 1.17 1999/04/11 12:55:43 mirar Exp $ $Id: colortable_lookup.h,v 1.9 1999/04/10 02:02:07 mirar Exp $ $Id: dct.c,v 1.14 1999/06/18 19:19:20 mirar Exp $ $Id: any.c,v 1.16 2000/03/22 18:12:19 peter Exp $ $Id: bmp.c,v 1.20 2000/02/03 12:25:18 grubba Exp $ $Id: gif.c,v 1.50 1999/11/14 22:16:08 mast Exp $ $Id: gif_lzw.c,v 1.6 1999/05/30 20:11:14 mirar Exp $ $Id: gif_lzw.h,v 1.7 1999/05/30 20:11:15 mirar Exp $ $Id: ilbm.c,v 1.13 2000/02/03 19:01:29 grubba Exp $ $Id: pnm.c,v 1.20 1999/06/19 11:08:00 mirar Exp $ $Id: x.c,v 1.26 2000/03/27 07:42:35 hubbe Exp $ $Id: xwd.c,v 1.13 2000/01/11 01:42:36 mast Exp $ $Id: font.c,v 1.58 2000/03/25 23:34:32 hubbe Exp $ $Id: image.c,v 1.160 2000/04/09 06:15:17 per Exp $ $Id: image.h,v 1.35 1999/09/25 19:58:48 grubba Exp $ $Id: layers.c,v 1.43 2000/02/22 03:41:27 per Exp $ $Id: matrix.c,v 1.22 2000/04/09 06:15:17 per Exp $ $Id: operator.c,v 1.25 1999/09/14 23:17:15 marcus Exp $ $Id: orient.c,v 1.13 1999/06/19 20:24:48 hubbe Exp $ $Id: pattern.c,v 1.18 1999/07/16 11:44:20 mirar Exp $ $Id: poly.c,v 1.3 1999/07/28 09:26:20 mirar Exp $ $Id: polyfill.c,v 1.30 1999/06/19 20:24:49 hubbe Exp $ Experimental functions.
basic:
clear,
clone,
create,
xsize,
ysize
plain drawing:
box,
circle,
getpixel,
line,
setcolor,
setpixel,
threshold,
polyfill
operators:
`&,
`*,
`+,
`-,
`==,
`>,
`<,
`|
pasting images:
paste,
paste_alpha,
paste_alpha_color,
paste_mask
getting subimages, scaling, rotating:
autocrop,
clone,
copy,
dct,
mirrorx,
rotate,
rotate_ccw,
rotate_cw,
rotate_expand,
scale,
skewx,
skewx_expand,
skewy,
skewy_expand
calculation by pixels:
apply_matrix,
change_color,
color,
distancesq,
grey,
invert,
modify_by_intensity,
outline
select_from,
rgb_to_hsv,
hsv_to_rgb,
average, max, min, sum, sumf, find_min, find_max
special pattern drawing:
noise,
turbulence,
test,
tuned_box,
gradients,
random
2 2 pixel(x,y)= base+ k ( sum sum pixel(x+k-1,y+l-1)*matrix(k,l) ) k=0 l=0
1/k is sum of matrix, or sum of matrix multiplied with div. base is given by r,g,b and is normally black.
blur (ie a 2d gauss function):
({({1,2,1}),
({2,5,2}),
({1,2,1})})
|
|
|
| original | ||
sharpen (k>8, preferably 12 or 16):
({({-1,-1,-1}),
({-1, k,-1}),
({-1,-1,-1})})
|
| |
edge detect:
({({1, 1,1}),
({1,-8,1}),
({1, 1,1})})
|
| |
horisontal edge detect (get the idea):
({({0, 0,0}),
({1,-2,1}),
({0, 0,0})})
|
| |
emboss (might prefer to begin with a grey image):
({({2, 1, 0}),
({1, 0,-1}),
({0,-1,-2})}), 128,128,128, 3
|
|
|
| greyed |
This function is not very fast.
| argument(s) | description |
|
array(array(int|array(int))) | the matrix; innermost is a value or an array with red, green, blue values for red, green, blue separation. |
|
int r int g int b | base level of result, default is zero |
|
int|float div | division factor, default is 1.0. |
This function is not very fast.
| argument(s) | description |
|
array(array(int|array(int))) | the matrix; innermost is a value or an array with red, green, blue values for red, green, blue separation. |
|
int r int g int b | base level of result, default is zero |
|
int|float div | division factor, default is 1.0. |
"Unneccesary" is all pixels that are equal -- ie if all the same pixels to the left are the same color, that column of pixels are removed.
The find_autocrop() function simply returns x1,y1,x2,y2 for the kept area. (This can be used with copy later.)
| argument(s) | description |
|
int border int left int right int top int bottom | which borders to scan and cut the image;
a typical example is removing the top and bottom unneccesary
pixels:
img=img->autocrop(0, 0,0,1,1); |
average() and sumf() may also wrap, but on a line basis. (Meaning, be careful with images that are wider than 8425104 pixels.) These functions may have a precision problem instead, during to limits in the 'double' C type and/or 'float' Pike type.
| argument(s) | description |
|
float factor | factor to use for both x and y |
|
float xfactor float yfactor | separate factors for x and y |
| argument(s) | description |
|
int newxsize int newysize | new image size in pixels |
![]() |
![]() |
| original | ->box |
| argument(s) | description |
|
int x1 int y1 int x2 int y2 | box corners |
|
int r int g int b | color of the box |
|
int alpha | alpha value |
| argument(s) | description |
|
int tor int tog int tob | destination color and next current color |
|
int fromr int fromg int fromb | source color, default is current color |
![]() |
![]() |
| original | ->circle |
| argument(s) | description |
|
int x int y | circle center |
|
int rx int ry | circle radius in pixels |
|
int r int g int b | color |
|
int alpha | alpha value |
![]() |
![]() |
| original | ->clear |
| argument(s) | description |
|
int r int g int b | color of the new image |
|
int alpha | new default alpha channel value |
![]() |
![]() |
![]() |
| original | clone | clone(50,50) |
| argument(s) | description |
|
int xsize int ysize | size of (new) image in pixels, called image is cropped to that size |
|
int r int g int b | current color of the new image, default is black. Will also be the background color if the cloned image is empty (no drawing area made). |
|
int alpha | new default alpha channel value |
The red, green and blue values of the pixels are multiplied with the given value(s). This works best on a grey image...
The result is divided by 255, giving correct pixel values.
If no arguments are given, the current color is used as factors.
![]() |
![]() |
| original | ->color(128,128,255); |
| argument(s) | description |
|
int r int g int b | red, green, blue factors |
|
int value | factor |
![]() |
![]() |
![]() |
| original | ->copy |
->copy |
| argument(s) | description |
|
int x1 int y1 int x2 int y2 | The requested new area. Default is the old image size. |
|
int r int g int b | color of the new image |
|
int alpha | new default alpha channel value |
![]() |
![]() |
| Image.Image |
Image.Image |
The image can also be calculated from some special methods, for convinience:
channel modes; followed by a number of 1-char-per-pixel strings or image objects (where red channel will be used), or an integer value: "grey" : make a grey image (needs 1 source: grey) "rgb" : make an rgb image (needs 3 sources: red, green and blue) "cmyk" : make a rgb image from cmyk (cyan, magenta, yellow, black)generate modes; all extra arguments is given to the generation function. These has the same name as the method: "test," "gradients" "noise" "turbulence" "random" "randomgrey" specials cases: "tuned_box" (coordinates is automatic)
| argument(s) | description |
|
int xsize int ysize | size of (new) image in pixels |
|
Color color int r int g int b | background color (will also be current color), default color is black |
|
int alpha | default alpha channel value |
Method for scaling is rather complex; the image is transformed via a cosine transform, and then resampled back.
This gives a quality-conserving upscale, but the algorithm used is n*n+n*m, where n and m is pixels in the original and new image.
Recommended wrapping algorithm is to scale overlapping parts of the image-to-be-scaled.
This functionality is actually added as an true experiment, but works...
| argument(s) | description |
|
int newx int newy | new image size in pixels |
It write's dots on stderr, to indicate some sort of progress. It doesn't use any fct (compare: fft) algorithms.
The given value (or current color) are used for coordinates in the color cube. Each resulting pixel is the distance from this point to the source pixel color, in the color cube, squared, rightshifted 8 steps:
p = pixel color o = given color d = destination pixel d.red=d.blue=d.green= ((o.red-p.red)²+(o.green-p.green)²+(o.blue-p.blue)²)>>8
![]() |
![]() |
![]() |
![]() |
| original | distance² to cyan | ...to purple | ...to yellow |
| argument(s) | description |
|
int r int g int b | red, green, blue coordinates |
| argument(s) | description |
|
int r int g int b | weight of color, default is r=87,g=127,b=41, same as the grey() method. |
Intensity of new pixels are calculated by:
i' = i^g
For example, you are viewing your image on a screen with gamma 2.2. To correct your image to the correct gamma value, do something like:
my_display_image(my_image()->gamma(1/2.2);
| argument(s) | description |
|
int g int gred int ggreen int gblue | gamma value |
| argument(s) | description |
|
int x int y | position of the pixel |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
| original | 2 color gradient |
10 color gradient |
3 colors, grad=4.0 |
3 colors, grad=1.0 |
3 colors, grad=0.25 |
![]() |
![]() |
![]() |
| original | ->grey(); | ->grey(0,0,255); |
| argument(s) | description |
|
int r int g int b | weight of color, default is r=87,g=127,b=41, which should be pretty accurate of what the eyes see... |
When converting to RGB, the input data is asumed to be placed in the pixels as above.
![]() |
![]() |
![]() |
| original | ->hsv_to_rgb(); | ->rgb_to_hsv(); |
![]() |
![]() |
![]() |
| tuned box (below) | the rainbow (below) | same, but rgb_to_hsv() |
HSV to RGB calculation:
in = input pixel out = destination pixel h=-pos*c_angle*3.1415/(float)NUM_SQUARES; out.r=(in.b+in.g*cos(in.r)); out.g=(in.b+in.g*cos(in.r + pi*2/3)); out.b=(in.b+in.g*cos(in.r + pi*4/3));
RGB to HSV calculation: Hmm.
Example: Nice rainbow.
object i = Image.Image(200,200);
i = i->tuned_box(0,0, 200,200,
({ ({ 255,255,128 }), ({ 0,255,128 }),
({ 255,255,255 }), ({ 0,255,255 })}))
->hsv_to_rgb();
![]() |
![]() |
![]() |
| original | ->invert(); | ->rgb_to_hsv()->invert()->hsv_to_rgb(); |
![]() |
![]() |
| original | ->line |
| argument(s) | description |
|
int x1 int y1 int x2 int y2 | line endpoints |
|
int r int g int b | color |
|
int alpha | alpha value |
//Stina is an image with a cat. array(object) Stina4=Stina->orient4(); Stina4[1]*=215; Stina4[3]*=215; string foo=Stina->make_ascii(@Stina4,40,4,8);
| / - \ hue= 0 64 128 192 (=red in an hsv image)
Replacement examples:
Old code:
img=map_fs(img->select_colors(200));New code:
img=Image.Colortable(img,200)->floyd_steinberg()->map(img);
Old code:
img=map_closest(img->select_colors(17)+({({255,255,255}),({0,0,0})}));
New code:
img=Image.Colortable(img,19,({({255,255,255}),({0,0,0})}))->map(img);
new pixel value = sum( my_abs(needle_pixel-haystack_pixel))
The new image only have the red rgb-part set.
| argument(s) | description |
|
int|float scale | Every pixel is divided with this value. Note that a proper value here depends on the size of the neadle. |
|
object needle | The image to use for the matching. |
|
object haystack_cert | This image should be the same size as the image itselves. A non-white-part of the haystack_cert-image modifies the output by lowering it. |
|
object needle_cert | The same, but for the needle-image. |
|
int foo object haystack_avoid | This image should be the same size as the image itselves. If foo is less than the red value in haystack_avoid the corresponding matching-calculating is not calculated. The avoided parts are drawn in the color 0,100,0. |
![]() |
![]() |
| original | ->mirrorx(); |
![]() |
![]() |
| original | ->mirrory(); |
For each color an intensity is calculated, from r, g and b factors (see grey), this gives a value between 0 and max.
The color is then calculated from the values given, v1 representing the intensity value of 0, vn representing max, and colors between representing intensity values between, linear.
![]() |
![]() |
| original | ->grey()->modify_by_intensity(1,0,0, 0,({255,0,0}),({0,255,0})); |
| argument(s) | description |
|
int r int g int b | red, green, blue intensity factors |
|
int|array(int) v1 int|array(int) vn | destination color |
The random seed may be different with each instance of pike.
Example:
->noise( ({0,({255,0,0}), 0.3,({0,255,0}), 0.6,({0,0,255}), 0.8,({255,255,0})}), 0.2,0.0,0.0,1.0 );
| argument(s) | description |
|
array(float|int|array(int)) colorrange | colorrange table |
|
float scale | default value is 0.1 |
|
float xdiff float ydiff | default value is 0,0 |
|
float cscale | default value is 1 |
orient gives an HSV image (run a hsv_to_rgb pass on it to get a viewable image). corresponding to the angle of the orientation:
| / - \ hue= 0 64 128 192 (=red in an hsv image) purple cyan green redRed, green and blue channels are added and not compared separately.
If you first use orient4 you can give its output as input to this function.
The orient4 function gives back 4 image objects, corresponding to the amount of different directions, see above.
Default is to paint above, below, to the left and the right of these pixels.
You can also run your own outline mask.
The outline_mask function gives the calculated outline as an alpha channel image of white and black instead.
![]() |
![]() |
![]() |
| original | masked through threshold |
...and outlined with red |
| argument(s) | description |
|
array(array(int)) mask | mask matrix. Default is ({({0,1,0}),({1,1,1}),({0,1,0})}). |
|
int olr int olg int olb | outline color. Default is current. |
|
int bkgr int bkgg int bkgb | background color (what color to outline to); default is color of pixel 0,0. |
|
int|float div | division factor, default is 1.0. |
| argument(s) | description |
|
object image | image to paste (may be empty, needs to be an image object) |
|
int x int y | where to paste the image; default is 0,0 |
An alpha channel value of 0 leaves nothing of the original image in the paste area, 255 is meaningless and makes the given image invisible.
| argument(s) | description |
|
object image | image to paste |
|
int alpha | alpha channel value |
|
int x int y | where to paste the image; default is 0,0 |
A pixel value of 255 makes the result become the color given, 0 doesn't change anything.
The masks red, green and blue values are used separately. If no color are given, the current is used.
| argument(s) | description |
|
object mask | mask image |
|
int r int g int b | what color to paint with; default is current |
|
int x int y | where to paste the image; default is 0,0 |
A pixel value of 255 makes the result become a pixel from the given image, 0 doesn't change anything.
The masks red, green and blue values are used separately.
| argument(s) | description |
|
object image | image to paste |
|
object mask | mask image |
|
int x int y | where to paste the image; default is 0,0 |
phaseh gives an image where
max falling min rising value= 0 64 128 192
0 is set if there is no way to determine if it is rising or falling. This is done for the every red, green and blue part of the image.
Phase images can be used to create ugly effects or to find meta-information in the orginal image.
![]() |
![]() |
![]() |
![]() |
![]() |
| original | phaseh() | phasev() | phasevh() | phasehv() |
| argument(s) | description |
|
array(int|float) curve | curve(s), ({x1,y1,x2,y2,...,xn,yn}),
automatically closed.
If any given curve is inside another, it will make a hole. |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
| original | ->random() | ->random(17) | greyed (same again) |
color(red) (same again) |
...red channel |
Use with ->grey() or ->color() for one-color-results.
The string is nullpadded or cut to fit.
| argument(s) | description |
|
string what | the hidden message |
![]() |
![]() |
![]() |
| original | ->rotate(15,255,0,0); | ->rotate_expand(15); |
The "expand" variant of functions stretches the image border pixels rather then filling with the given or current color.
This rotate uses the skewx() and skewy() functions.
| argument(s) | description |
|
int|float angle | the number of degrees to rotate |
|
int r int g int b | color to fill with; default is current |
![]() |
![]() |
| original | ->rotate_ccw(); |
![]() |
![]() |
| original | ->rotate_cw(); |
| argument(s) | description |
|
float factor | factor to use for both x and y |
|
float xfactor float yfactor | separate factors for x and y |
| argument(s) | description |
|
int newxsize int newysize | new image size in pixels |
This is very close to a floodfill.
The image is scanned from the given pixel, filled with 255 if the color is the same, or 255 minus distance in the colorcube, squared, rightshifted 8 steps (see distancesq).
When the edge distance is reached, the scan is stopped. Default edge value is 30. This value is squared and compared with the square of the distance above.
| argument(s) | description |
|
int x int y | originating pixel in the image |
| argument(s) | description |
|
int r int g int b | new color |
|
int alpha | new alpha value |
![]() |
![]() |
| original | ->setpixel |
| argument(s) | description |
|
int x int y | position of the pixel |
|
int r int g int b | color |
|
int alpha | alpha value |
![]() |
![]() |
![]() |
| original | ->skewx(15,255,0,0); | ->skewx_expand(15); |
| argument(s) | description |
|
int x | the number of pixels The "expand" variant of functions stretches the image border pixels rather then filling with the given or current color. |
|
float yfactor | best described as: x=yfactor*this->ysize() |
|
int r int g int b | color to fill with; default is current |
![]() |
![]() |
![]() |
| original | ->skewy(15,255,0,0); | ->skewy_expand(15); |
The "expand" variant of functions stretches the image border pixels rather then filling with the given or current color.
| argument(s) | description |
|
int y | the number of pixels |
|
float xfactor | best described as: t=xfactor*this->xsize() |
|
int r int g int b | color to fill with; default is current |
![]() |
![]() |
![]() |
| original | ->test() | ...and again |
If any of red, green, blue parts of a pixel is larger then the given value, the pixel will become white, else black.
This method works fine with the grey method.
If no arguments are given, it will paint all non-black pixels white. (Ie, default is 0,0,0.)
![]() |
![]() |
![]() |
| original | ->threshold(100); | ->threshold(0,100,0); |
Tuning function is (1.0-x/xw)*(1.0-y/yw) where x and y is the distance to the corner and xw and yw are the sides of the rectangle.
![]() |
![]() |
![]() |
![]() |
| original | tuned box | solid tuning (blue,red,green,yellow) |
tuning transparency (as left + 255,128,128,0) |
| argument(s) | description |
|
int x1 int y1 int x2 int y2 | rectangle corners |
|
array(array(int)) corner_color | colors of the corners:
({x1y1,x2y1,x1y2,x2y2})
each of these is an array of integeres:
({r,g,b}) or ({r,g,b,alpha})
Default alpha channel value is 0 (opaque). |
The random seed may be different with each instance of pike.
Example:
->turbulence( ({0,({229,204,204}), 0.9,({229,20,20}), 0.9,Color.black}) );
| argument(s) | description |
|
array(float|int|array(int)) colorrange | colorrange table |
|
int octaves | default value is 3 |
|
float scale | default value is 0.1 |
|
float xdiff float ydiff | default value is 0,0 |
|
float cscale | default value is 1 |
| argument(s) | description |
|
object operand | the other image to divide with; the images must have the same size. |
|
Color color int value | if specified as color or value, it will act as a whole image of that color (or value). |
| argument(s) | description |
|
object operand | the other image to compare with; the images must have the same size. |
|
array(int) color | an array in format ({r,g,b}), this is equal to using an uniform-colored image. |
|
int value | equal to ({value,value,value}). |
Comparision is strict and on pixel-by-pixel basis. (Means if not all pixel r,g,b values are correct compared with the corresponding pixel values, 0 is returned.)
| argument(s) | description |
|
object operand | the other image to compare with; the images must have the same size. |
|
array(int) color | an array in format ({r,g,b}), this is equal to using an uniform-colored image. |
|
int value | equal to ({value,value,value}). |
a>=b and a<=b between objects is equal to !(a<b) and !(a>b), which may not be what you want (since both < and > can return false, comparing the same images).
This can be useful to lower the values of an image, making it greyer, for instance:
image=image*128+64;
| argument(s) | description |
|
object operand | the other image to multiply with; the images must have the same size. |
|
array(int) color | an array in format ({r,g,b}), this is equal to using an uniform-colored image. |
|
int value | equal to ({value,value,value}). |
| argument(s) | description |
|
object operand | the image which to add. |
|
array(int) color | an array in format ({r,g,b}), this is equal to using an uniform-colored image. |
|
int value | equal to ({value,value,value}). |
| argument(s) | description |
|
object operand | the other image to compare with; the images must have the same size. |
|
array(int) color | an array in format ({r,g,b}), this is equal to using an uniform-colored image. |
|
int value | equal to ({value,value,value}). |
| argument(s) | description |
|
object operand | the other image to compare with; the images must have the same size. |
|
array(int) color | an array in format ({r,g,b}), this is equal to using an uniform-colored image. |
|
int value | equal to ({value,value,value}). |
The object has color reduction, quantisation, mapping and dithering capabilities.
add takes the same argument(s) as create, thus adding colors to the colortable.
The colortable is mostly a list of colors, or more advanced, colors and weight.
The colortable could also be a colorcube, with or without additional scales. A colorcube is the by-far fastest way to find colors.
Example:
ct=colortable(my_image,256); // the best 256 colors
ct=colortable(my_image,256,({0,0,0})); // black and the best other 255
ct=colortable(({({0,0,0}),({255,255,255})})); // black and white
ct=colortable(6,7,6); // a colortable of 252 colors
ct=colortable(7,7,5, ({0,0,0}),({255,255,255}),11);
// a colorcube of 245 colors, and a greyscale of the rest -> 256
| argument(s) | description |
|
array(array(int)) colors | list of colors |
|
object(Image.Image) image | source image |
|
int number | number of colors to get from the image
0 (zero) gives all colors in the image. Default value is 256. |
|
array(array(int)) needed | needed colors (to optimize selection of others to these given)
this will add to the total number of colors (see argument 'number') |
|
int r int g int b | size of sides in the colorcube, must (of course) be equal or larger than 2 - if smaller, the cube is ignored (no colors). This could be used to have only scales (like a greyscale) in the output. |
|
array(int) fromi array(int) toi int stepi | This is to add the possibility of adding a scale
of colors to the colorcube; for instance a grayscale
using the arguments ({0,0,0}),({255,255,255}),17,
adding a scale from black to white in 17 or more steps.
Colors already in the cube is used again to add the number of steps, if possible. The total number of colors in the table is therefore r*b*g+step1+...+stepn. |
example: (mapping)Image.Colortable(img)
| argument(s) | description |
|
string to | must be "string", "array" or "mapping". |
The colorspace is divided in small cubes, each cube containing the colors in that cube. Each cube then gets a list of the colors in the cube, and the closest from the corners and midpoints between corners.
When a color is needed, the algorithm first finds the correct cube and then compares with all the colors in the list for that cube.
example: colors=Image.Colortable(img)->cubicles();
algorithm time: between O[m] and O[m * n], where n is numbers of colors and m is number of pixels
The arguments can be heavy trimmed for the usage of your colortable; a large number (10×10×10 or bigger) of cubicles is recommended when you use the colortable repeatedly, since the calculation takes much more time than usage.
recommended values:
image size setup 100×100 cubicles(4,5,4) (default) 1000×1000 cubicles(12,12,12) (factor 2 faster than default)
In some cases, the full method is faster.
![]() |
![]() |
![]() |
| original | default cubicles, 16 colors |
accuracy=200 |
| argument(s) | description |
|
int r int g int b | Size, ie how much the colorspace is divided. Note that the size of each cubicle is at least about 8b, and that it takes time to calculate them. The number of cubicles are r*g*b, and default is 4,5,4, ie 80 cubicles. This works good for 200±100 colors. |
|
int accuracy | Accuracy when checking sides of cubicles. Default is 16. A value of 1 gives complete accuracy, ie cubicle() method gives exactly the same result as full(), but takes (in worst case) 16× the time to calculate. |
Not applicable to colorcube types of colortable.
The arguments to this method is for fine-tuning of the algorithm (for computer graphics wizards).
![]() |
![]() |
![]() |
| original | floyd_steinberg to a 4×4×4 colorcube | floyd_steinberg to 16 chosen colors |
| argument(s) | description |
|
int bidir | Set algorithm direction of forward. -1 is backward, 1 is forward, 0 for toggle of direction each line (default). |
|
int|float forward int|float downforward int|float down int|float downback | Set error correction directions. Default is forward=7, downforward=1, down=5, downback=3. |
|
int|float factor | Error keeping factor. Error will increase if more than 1.0 and decrease if less than 1.0. A value of 0.0 will cancel any dither effects. Default is 0.95. |
example: colors=Image.Colortable(img)->full();
algorithm time: O[n*m], where n is numbers of colors and m is number of pixels
each pixel in the image object is an entry in the colortable
![]() |
![]() |
![]() |
![]() |
![]() |
no dither | |
![]() |
![]() |
![]() |
![]() |
![]() |
floyd_steinberg dither | |
![]() |
![]() |
![]() |
![]() |
![]() |
ordered dither | |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
randomcube dither |
| original | 2 | 4 | 8 | 16 | 32 colors |
![]() |
![]() |
![]() |
![]() |
| original | mapped to Image.Colortable(6,6,6)-> |
||
| ordered (42,42,42,2,2) |
ordered() | ordered (42,42,42, 8,8, 0,0, 0,1, 1,0) |
|
![]() |
![]() |
![]() |
![]() |
| argument(s) | description |
|
int r int g int b | The maximum error. Default is 32, or colorcube steps (256/size). |
|
int xsize int ysize | Size of error matrix. Default is 8×8. Only values which factors to multiples of 2 and 3 are possible to choose (2,3,4,6,8,12,...). |
|
int x int y int rx int ry int gx int gy int bx int by | Offset for the error matrix. x and y is for both red, green and blue values, the other is individual. |
The randomgrey method uses the same random error on red, green and blue and the randomcube method has three random errors.
![]() |
![]() |
![]() |
| original | mapped to Image.Colortable(4,4,4)-> |
|
| randomcube() | randomgrey() | |
![]() |
![]() |
![]() |
| argument(s) | description |
|
int r int g int b int err | The maximum error. Default is 32, or colorcube step. |
All needed (see create) colors are kept.
reduce_fs creates and keeps the outmost corners of the color space, to improve floyd-steinberg dithering result. (It doesn't work very well, though.)
| argument(s) | description |
|
int colors | target number of colors |
reduce_fs keeps the "corners" as "needed colors".
This is a very simple way of finding the correct color. The algorithm initializes a cube with r x g x b colors, where every color is chosen by closest match to the corresponding coordinate.
This format is not recommended for exact match, but may be usable when it comes to quickly view pictures on-screen.
It has a high init-cost and low use-cost. The structure is initiated at first usage.
Default factors are 3, 4 and 1; blue is much darker than green. Compare with Image.Image->grey().
| argument(s) | description |
|
object(Colortable) with | Colortable object with colors to add |
| argument(s) | description |
|
object(Colortable) with | Colortable object with colors to subtract |
(What really happens is that the image and alpha channel is checked, and edges equal the fill setup is cropped away.)
find_autocrop() returns an array of xoff,yoff,xsize,ysize, which can be fed to crop().
left...bottom arguments can be used to tell what sides cropping are ok on.
"normal", "add", "subtract", "multiply", "divide", "modulo", "invsubtract", "invdivide", "invmodulo", "difference", "max", "min", "bitwise_and", "bitwise_or", "bitwise_xor",
"replace", "red", "green", "blue",
"replace_hsv", "hue", "saturation", "value", "color",
"darken", "lighten",
"dissolve", "behind", "erase",
available_modes() simply gives an array containing the names of these modes.
"image":image, // default: blackThe layer can also be created "empty", either giving a size and color - this will give a filled opaque square, or a color, which will set the "fill" values and fill the whole layer with an opaque color."alpha":alpha, // alpha channel object // default: full opaque
"mode":string mode, // layer mode, see mode. // default: "normal"
"alpha_value":float(0.0-1.0), // layer general alpha value // default is 1.0; this is multiplied // with the alpha channel.
"xoffset":int, "yoffset":int, // offset of this layer
"fill":Color, "fill_alpha":Color, // fill color, ie what color is used // "outside" the image. default: black // and black (full transparency).
"tiled":int(0|1), // select tiling; if 1, the image // will be tiled. deafult: 0, off
All values can be modified after object creation.
The fill values are used if the layer is enlarged.
As an example, the XCF and PSD image decoders set the 'name' attribute to the name the layer had in the source file.
For simple usage, see write and load.
other methods: baseline, height, set_xspacing_scale, set_yspacing_scale, text_extents
struct file_head
{
unsigned INT32 cookie; - 0x464f4e54
unsigned INT32 version; - 1
unsigned INT32 chars; - number of chars
unsigned INT32 height; - height of font
unsigned INT32 baseline; - font baseline
unsigned INT32 o[1]; - position of char_head's
} *fh;
struct char_head
{
unsigned INT32 width; - width of this character
unsigned INT32 spacing; - spacing to next character
unsigned char data[1]; - pixmap data (1byte/pixel)
} *ch;
version 2:
On-disk syntax (everything in N.B.O), int is 4 bytes, a byte is 8 bits:
pos
0 int cookie = 'FONT'; or 0x464f4e54
4 int version = 2; 1 was the old version without the last four chars
8 int numchars; Always 256 in this version of the dump program
12 int height; in (whole) pixels
16 int baseline; in (whole) pixels
20 char direction; 1==right to left, 0 is left to right
21 char format; Font format
22 char colortablep; Colortable format
23 char kerningtablep; Kerning table format
24 int offsets[numchars]; pointers into the data, realative to &cookie.
[colortable]
[kerningtable]
At each offset:
0 int width; in pixels
4 int spacing; in 1/1000:th of a pixels
8 char data[]; Enough data to plot width * font->height pixels
Please note that if width is 0, there is no data.
Font formats:
id type
0 Raw 8bit data
1 RLE encoded data, char length, char data, 70% more compact than raw data
2 ZLib compressed data 60% more compact than RLE
Colortable types:
0 No colortable (the data is an alpha channel)
1 24bit RGB with alpha (index->color, 256*4 bytes, rgba)
2 8bit Greyscale with alpha (index->color, 256*2 bytes)
Kerningtable types:
0 No kerning table
1 numchars*numchars entries, each a signed char with the kerning value
2 numchar entries, each with a list of kerning pairs, like this:
int len
len * (short char, short value)
**!