diff --git a/doc/manual/example1 b/doc/manual/example1 new file mode 100644 index 0000000000000000000000000000000000000000..63842954901a938fa8f5332d01e6ec4020faa0de --- /dev/null +++ b/doc/manual/example1 @@ -0,0 +1,138 @@ +1. Let's write a small uLPC program: + + int main() + { + write("hello world\n"); + return 0; + } + + Let's call this file hello_world.ulpc, and then we try to run it: + + $ ulpc hello_world.lpc + hello world + $ + +1.1 Pretty simple, Let's see what everything means: + + int main() + + This begins the function main. Before the function name the type of value + it returns is declared, in this case 'int'. The empty space between the + parethesis indicates that this function takes no arguments. The function + main is special in the sense that it is the first function called when + your program starts. Everything your program does should start in main. + + { + write("hello world\n"); + return 0; + } + + This is the body of the function. The brackets group together a series of + statements into a block which will be executed when this function is called. + The statements aer separated by semi-colons. + + write("hello world\n"); + + The first statement is a call to the builtin function write. To this + function the constant string "hello world\n" is sent. write then of + course writes this string to stdout when executed. + + return 0; + + This statement aborts the function and returns the value zero. Any statements + following the return statements (not that there are any in this example) + will not be executed. + +1.2 Improving hello_world.lpc + Typing 'ulpc hello_world.lpc' to run our program may seem a bit unpractical, + fortunately, UNIX provides us with a way of automating this somewhat. + If we modify hello_world.lpc to look like this: + + #!/usr/local/bin/ulpc + + int main() + { + write("hello world\n"); + } + + And then we tell UNIX that hello_world.lpc is executable: + + $ chmod +x hello_world.lpc + + Now we can run hello_world.lpc without having to bother about running + ulpc: + + $ ./hello_wold.lpc + hello world + $ + + + NB. The hash bang (#!) must be absolutely first in the file, no empty + lines or whitespaces can precede it for this to work. The file after + the hash bang must also be the complete filename to the ulpc binary, + and it may not exceed 30 characters. + +1.3 A better hello_world.lpc + Now, wouldn't it be nice if it said "Hello world!" instead of "hello world" ? + But of course we don't want to make our program incompatible, so we'll add + a command line option that will make it type the old "hello world". + This is what it could look like: + + #!/usr/local/bin/ulpc + + int main(int argc, string *argv) + { + if(argc > 2 && argv[1]=="--traditional") + { + write("hello world\n"); // old stype + }else{ + write("Hello world!\n"); // new style + } + return 0; + } + + Let's run it: + + $ chmod +x hello_world.lpc + $ ./hello_world.lpc + Hello world! + $ ./hello_world.lpc --traditional + hello world + $ + + What's been added? + + int main(int argc, string *argv) + + Here the space between the parentesis 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 'string *'. The star + means that the argument is an array, in this case an array of strings. + + The arguments to main are taken from the command line when the uLPC 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. + + if(argc > 2 && argv[1] == "--traditional") + { + write("hello world\n"); // old stype + }else{ + write("Hello world!\n"); // new style + } + + 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: + + argc > 2 && argv[1] == "--traditional" + + Loosely translated, this means: argc is greater than two and the second + element in the array argv is equal to the string "--traditional". + + Also note the comments: + + write("hello world\n"); // old stype + + The // begins a comment which continues to the end of the line. diff --git a/doc/manual/example2 b/doc/manual/example2 new file mode 100644 index 0000000000000000000000000000000000000000..394bc08f6fa3962f1c339d351107457c0d01344d --- /dev/null +++ b/doc/manual/example2 @@ -0,0 +1,136 @@ + Now we will look at a slightly more useful example: + + #!/usr/local/bin/ulpc + + int main(int argc,string *argv) + { + int i; + string file; + + if(argc<4) + { + perror("Usage: rsif <from> <to> <files>\n"); + return 1; + } + + for(i=3; i<argc; i++) + { + if(file=read_bytes(argv[i])) + { + if(-1!=strstr(file,argv[1])) + { + write("Processing "+argv[i]+".\n"); + file=replace(file,argv[1],argv[2]); + + if( mv(argv[i],argv[i]+"~") ) + { + write_file(argv[i],file); + }else{ + write("Failed to create backup file.\n"); + } + } + } + } + + return 0; + } + + This program is called 'rsif' and comes with the uLPC distribution. + What it does is that it takes two strings and a bunch of files and + replaces every occurance of the first string with the second one in + each of these files. As you might have guessed 'rsif' is short for + Replace String In File. Line by line, this is how the program works: + + #!/usr/local/bin/ulpc + + Make UNIX run ulpc to interpret this program. + + int main(int argc,string *argv) + { + + Start the function main, it will return an int, and take the command line + arguments into the parameters argc and argv. + + int i; + string file; + + Declare two temporary variables to use later. The first one 'i' is an + integer, and the second one 'file' is a string. + + if(argc<4) + { + perror("Usage: rsif <from> <to> <files>\n"); + return 1; + } + + If argc is lesser than four it means that the program received less than + three arguments. Since rsif needs at least three arguments we write how + to use rsif to stderr using the function perror and then exit this function + with the return value 1. Nothing after the return will be executed if + argc is lesser than 4. + + + for(i=3; i<argc; i++) + { + + This statement starts a loop. It will first set i to three, then execute + everything within the brackets for as long as i is lesser than argc. After + each time the block between the brackets the final statement, i++, will + be executed. ++ is the increment operator, it will increase i by one. + + if(file=read_bytes(argv[i])) + { + + Look closely, the experssion in this if-statement is not a comparison, it + is in fact an assignment. Thus file will be assigned the value returned by + read_bytes. When an assignment is used as an expression it has the same + value as the left hand side of the assignment. (in this case the return + value from read_bytes) So what this does is: it reads the contents of the + file with the filename taken from argv[i] and assigns the contents to the + variable file. If there is no file with that name, zero will be returned + and then the block within the brackets will not be executed. + + if(-1!=strstr(file,argv[1])) + { + + The function strstr searches for a string in in a string and returns the + first position where it is found and -1 if not. This if statement simply + checks if argv[1] (the 'from' string) is present in the file. If it is + not we won't need to do anything more about this file. The != operator + means 'not equal to'. + + write("Processing "+argv[i]+".\n"); + + Write that we are processing this file to stdout. + + file=replace(file,argv[1],argv[2]); + + Call the builtin function replace and replace oall occurances of the 'from' + string with the 'to' string and assign the new result to the variable 'file'. + + if( mv(argv[i],argv[i]+"~") ) + { + Try moving the file argv[i] to a backup file by adding a tilde to the end + of the name. Then choose action depending on weather it worked or not. + + write_file(argv[i],file); + + If it worked we re-create the file argv[i] by writing the string 'file' to + it. + + }else{ + write("Failed to create backup file.\n"); + + If the mv didn't work we write a message stating so to stdout. + + } + } + } + } + + return 0; + } + + + Then we end all the blocks and return zero. A return value of zero from main + indicates success, 1 or more means failiure. diff --git a/doc/manual/example3 b/doc/manual/example3 new file mode 100644 index 0000000000000000000000000000000000000000..2323435af7d952e9ba04ae4c13cf3d7b0901c02e --- /dev/null +++ b/doc/manual/example3 @@ -0,0 +1,269 @@ + This example is a very simple www-server. + + + #!/usr/local/bin/ulpc + + /* A very small httpd capable of fetching files only. + * Written by Fredrik H�binette as a demonstration of uLPC. + */ + + A comment, /* begins the comment, and */ ends it. + + inherit "/precompiled/port"; + + Inherit copies all the functionality of /precompiled/port into this program. + /precompiled/port makes it possible to bind a TCP socket to accept incoming + connections. + + Next are some constants that will affect how uHTTPD will operate. This uses + the preprocessor directive #define. As an example, after the first define + below, BLOCK will be replaced with 16060. + + /* number of bytes to read for each write */ + #define BLOCK 16060 + + /* Where do we have the html files ? */ + #define BASE "/home/hubbe/ulpc/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 + + Next we declare a global variable of the type program called output_class, + and then we use the class construct to assign a program to it. class {} + defines a clonable program. (or class for you C++ freaks) + + program output_class=class + { + inherit "/precompiled/file" : socket; + inherit "/precompiled/file" : file; + + Our new class inherits /precompile/file twice. To be able to separate them + they are then named 'socket' and 'file'. + + int offset=0; + + Then ther is a global variable called offset which is initalized to zero. + (each instance of this class will have it's own instance of this variable, + so it is not truly global, but..) + Note that the initalization is done when the clas is cloned. (or instanciated + if you prefer C++ terminology) + + + Next we define the function write_callback(). '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. + + void write_callback() + { + int written; + string data; + + file::seek(offset); + + Move the file pointer to the where we want to the position we want to read + from. + + data=file::read(BLOCK); + + Read BLOCK (16060) bytes from the file. If there are less that that left to + read only that many bytes will be returned. + + if(strlen(data)) + { + + If we managed to read someting... + + written=socket::write(data); + + ... we try to write it to the socket. + + if(written >= 0) + { + offset+=written; + return; + } + + Update offset if we managed to write to the socket without errors. + + perror("Error: "+socket::errno()+".\n"); + } + + If something went wront during writing, or there was nothing left to read + we destruct this instance of this class. + + destruct(this_object()); + } + + That was the end of write_callback() + + + Next we need a variable to buffer the input received in. We initialize it + to an empty string. + + string input=""; + + And then we define the function that will be called when there is something + in the socket input buffer. Ignore the argument 'id' for now. The second + argument is the contents of the input buffer. + + void read_callback(mixed id,string data) + { + string cmd; + + input+=data; + + 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 ouputting the file. + + if(sscanf(input,"%s %s%*[\012\015 \t]",cmd,input)) + { + + This sscanf is pretty complicated, but in essense it means: put the + first word in 'input' in 'cmd' and the second in 'input' and return 2 + if successfull, 0 otherwise. + + if(cmd!="GET") + { + perror("Only method GET is supported.\n"); + destruct(this_object()); + return; + } + + If the first word isn't GET print an error message and terminate + this instance of the program. (and thus the connection) + + sscanf(input,"%*[/]%s",input); + + Remove the leading slash. + + input=combine_path(BASE,input); + + Combine the requested file with the base of the HTML tree, this gives + us a full filename beginning with a slash. + + if(!file::open(input,"r")) + { + + Try opening the file in read-only mode. If this fails, try opening NOFILE + instead. + + if(!file::open(NOFILE,"r")) + { + + If this fails too. Write an error message and destruct this object. + + perror("Couldn't find default file.\n"); + destruct(this_object()); + return; + } + } + + Ok, now we set up the socket so we can write the data back. + + socket::set_buffer(65536,"w"); + + Set the buffer size to 64 kilobytes. + + socket::set_nonblocking(0,write_callback,0); + + Make it so that write_callback is called when it is time to write more + data to the socket. + + write_callback(); + + Jump-start the writing. + } + } + + That was the end of read_callback(). + + + This function is called if the connection is closed while we are reading + from the socket. + void selfdestruct() { destruct(this_object()); } + + + This function is called when the program is instanciated. 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 create(object f) + { + socket::assign(f); + + We insert the data from the file f into 'socket'. + + socket::set_nonblocking(read_callback,0,selfdestruct); + + 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. + + } + + End of create() + + }; + + End of the new class. + + + Next we define the function called when someone connects. + + void accept_callback() + { + object tmp_output; + tmp_output=accept(); + + The function accept clones a /precompiled/file and makes this equal to the + newly connected socket. + + if(!tmp_output) return; + + If it failed we just return. + + clone(output_class, tmp_output); + + Otherwise we clone an instanec of 'output_class' and let it take care of the + connection. + + destruct(tmp_output); + + Destruct the object returned by accept(), output_class has already copied + the contents of this object. + + } + + + Then there is main, the function that gets it all started. + + int main(int argc, string *argv) + { + perror("Starting minimal httpd\n"); + + Write an encouraging message to stderr. + + if(!bind(PORT, accept_callback)) + { + perror("Failed to open socket (already bound?)\n"); + return 17; + } + + + 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 failiure. + + return - 17; /* Keep going */ + + If everything went ok, we return -17, any negative value returned by main() + means that the program WONT exit, it will hang around waiting for events + instead. (like someone connecting) + } + + End of uhttpd.lpc