#include #include #include #include #include #include #include "tlmplib.h" #include #include #include static long long getnow () { struct timeval tv; gettimeofday (&tv,NULL); return tv.tv_sec *(long long)1000000 + tv.tv_usec; } // #Specbeg: sample / loadfile component /* loadfile is a simple component to handle text file. */ static void sample_loadfile() { // This is a minimal case to print a file. THis is short and does // all the error reporting and testing. ("/proc/net/route",false); fputs (line,stdout); return 0; // Here is an example with more control ("/proc/net/route",true); tlmp_error ("No file /proc/net/route\n"); printf ("The file is empty\n"); // Ok, we know the file is not empty, we format a nice table printf ("This is the routing table\n"); printf ("-------------\n"); // Often a component pass "commodity" information // Here it passes the line number. We use this to skip the first line. // and we return -1 to print only the first 3 lines if (noline > 0) printf ("%s\n",line); return noline > 3 ? -1 : 0; } // #Specend: // #Specbeg: sample / savefile component /* savefile is a simple component to update files. */ static void sample_savefile() { // We just overwrite the content ("/tmp/x.log",false); // savefile does the sanity check and call us with a FILE handle fprintf (fout,"This is the content\n"); return 0; // We add a few lines ("/tmp/x.log",true); fprintf (fout,"This is more content\n"); return 0; tlmp_error ("Can't append to /tmp/x.log\n"); printf ("File /tmp/x.log was updated\n"); // Ok, the file is open in append mode. dowrite will be called printf ("Start the update\n"); } // #Specend: // #Specbeg: sample / walkfs component /* walkfs is used to browse into a directory. You can use this to extract some file from a sub-directory. At each step, you have enough information to tell if you continue, go deeper and so on. */ static void sample_walkfs() { ("/tmp"); // One directory entry, this can be a file or a directory printf ("%*.*s%s\n",depth*8,depth*8,"",basename); // We do not go too deep return depth < 2; } // #Specend: // #Specbeg: sample / walkpopen component /* walkpopen is used to execute a sub-program and grab the output. */ static void sample_walkpopen() { // A very short example to extract the output of a command ("ls -l",10); printf ("%s\n",line); return 0; // This time, we execute from the current directory // This will produce some error messages ("ls -l *.c *.missing",10,true); printf ("%s\n",line); return 0; printf ("ERROR: %s\n",line); return 0; } // #Specend: // #Specbeg: sample / copyfile component /* copyfile is used to copy file with some interactivity. */ static void sample_copyfile() { // We create a test file /tmp/toto.old ("/tmp/toto.old",false); for (int i=0; i<100000; i++){ fputs ("hello alalallllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n",fout); } return 0; // All functags do have defaults ("/tmp/toto.old","/tmp/toto.new"); // We produce a progress bar in ascii int pound_shown; glocal.pound_shown = 0; ("/tmp/toto.old","/tmp/toto.new"); fprintf (stderr,"Some error: %s\n",details); // We put a pound for every 100 kbytes copied int nbpound = sofar/(100*1024); for (; glocal.pound_shown < nbpound; glocal.pound_shown++){ fputc ('#',stdout); } fputc ('\n',stdout); // A progress bar in gui mode. We quit if more than 1meg is copied DIALOG dia; int gauge; glocal.gauge = 0; glocal.dia.newf_gauge ("Copying",glocal.gauge,100); int nof = 0; glocal.dia.show ("Copy files","",help_nil,nof,0); ("/tmp/toto.old","/tmp/toto.new"); glocal.gauge = sofar*100/size; glocal.dia.reload(); diagui_flush(); end = sofar > 1024*1024; xconf_notice ("Done"); } // #Specend: // #Specbeg: sample / streamp component /* streamp is a simple component to help split a streamed input into chunks. It does the buffering, filling and processing logic. The following example output lines (text lines) but read bytes 5 at a time. So fill() is called until process() sees a full line and output it. One way to test this sample is to copy a text file in /tmp/foo and do /tmp/x streamp /tmp/foo.new cmp /tmp/foo /tmp/foo.new && echo ok */ void sample_streamp() { (); return fread (buf,1,5,stdin); // Check if there is a end of line const void *pt = memchr (buf,'\n',len); int used = 0; if (pt != NULL){ used = (char*)pt - (char*)buf; printf ("%*.*s\n",used,used,(const char *)buf); used++; }else{ fprintf (stderr,"length %d is not enough\n",len); } return used; } // #Specend: // #Specbeg: sample / streamp_do component /* streamp_do is a simple component to help split some bytes into It receives some bytes, add them to an internal buffer (STREAMP_BUF) and tries to extract some chunks/records out of it (using the process functag). Unlike the streamp component, this one does not handle the fill function. It must be called several time, each time with more bytes to process. The following example output lines (text lines) but read bytes 5 at a time. So the component is called over and over. One way to test this sample is to copy a text file in /tmp/foo and do /tmp/x streamp_do /tmp/foo.new cmp /tmp/foo /tmp/foo.new && echo ok */ void sample_streamp_do() { // We create a very small buffer (1 byte) and let it grow 1 byte // at a time to show it adapts properly STREAMP_BUF ctl (1,1); char buf[5]; int n; while ((n=fread(buf,1,5,stdin))> 0){ (ctl,buf,n); // Check if there is a end of line const void *pt = memchr (buf,'\n',len); int used = 0; if (pt != NULL){ used = (char*)pt - (char*)buf; printf ("%*.*s\n",used,used,(const char *)buf); used++; }else{ // fprintf (stderr,"length %d is not enough\n",len); } return used; } } // #Specend: // #Specbeg: sample / STREAMP object /* STREAMP is an object to handling a stream of bytes and handling that to a processing unit expecting complete records. The object iterate between fill() functag, to grab more bytes and process() functag, to try to process some records. The process functag return how many bytes it used from the buffer, potentially 0 if it does not find a complete record to process. Then fill() is called again to grab more bytes and so on. The simple case is streamp which does everything. streamp is built on top of STREAMP, which is more flexible. The following example uses two STREAMP object, each doing half the job. */ void sample_STREAMP() { // This waits for empty line to output paragraphs (); // Check if there is a end of line const char *text = (const char *)buf; int ret = 0; if (nomore){ ret = len; }else{ for (int i=0; i 0){ printf ("\n"); printf ("%*.*s",ret,ret,text); printf ("\n"); } return ret; // This inputs 5 bytes at a time, and picks full line as record (); // printf ("filling\n"); return fread (buf,1,5,stdin); // Check if there is a end of line const void *pt = memchr (buf,'\n',len); int used = 0; if (pt != NULL){ used = (char*)pt - (char*)buf + 1; } // printf ("validrecord used=%d len=%d\n",used,len); return used; while(1){ char buf[100000]; int len = input.getrecord (buf,sizeof(buf)); if (len > 0){ output.fill (buf,len); }else{ break; } } output.eof (); } // #Specend: // #Specbeg: sample / COROUTINE object / principles /* Sometime we need to solve two problems at once. One for example manages complex parsing of some inputs and the other, the formatting of the results. Using object orientation is often a clean solution but tedious. For example, you may have a PARSER object called once to do some intialisation and later to retrieve one token at a time. Then the formatting part is simply a loop processing each token one after the other and performing various formatting (new line, new page). While a nice solution, the implementation of the PARSER object may be more complex than needed. The object must remember all the state in the parsing process and resume parsing each time a new token is request. The co-routine concept produces an elegant solution. Imagine two sub-routine each performing its task using loops inside loops and potentially recursion. Then deep down into some complex loop and if statements, one piece of information is identified, suitable for the other routine to process. The first routine then stops and let the other process the information. Once this is done, the second routine also stop and let the first resumes exactly where it was before it stops. This way the two routines are maintaining their complex state while sharing information without needing extra logic to re-enter the state (left when returning a token). The TLMP COROUTINE object lets you define a co-routine acting as a provider. The client code just iterate through the various results produced by the co-routine. The following example shows a co-routine reading a file and spitting out each words in it. The parent then print the words, 3 per lines. */ static void sample_coroutine0() { glocal SSTRING val; // Split a file into words (); ("/tmp/file",true); int ret = 0; const char *pt = line; while (ret == 0 && *pt != '\0'){ while (isspace(*pt))pt++; const char *start = pt; while (*pt > ' ') pt++; if (pt > start){ glocal.val.setfrom (start,pt-start); ret = glocal.COROUTINE.yield(); } } return ret; // The main routine just iterate over the result set while (!co.is_done()){ for (int i=0; i<3 && co.next(); i++){ printf (" --%s--",glocal.val.get()); } printf ("\n"); } } // #Specend: // #Specbeg: sample / testing COROUTINE /* The following code presents a very simple coroutine, but shows how we can interact. */ static void sample_costatus(COROUTINE &co, const char *msg) { printf ("%s Status idle=%d running=%d done=%d\n" ,msg,co.is_idle(),co.is_running(),co.is_done()); } static void sample_coroutine1() { glocal int result=-1; (); printf ("\tStarting\n"); for (int i=0; i<5; i++){ glocal.result = i; if (yield()==-1) break; } printf ("\tEnding\n"); sample_costatus(co,"Initial"); // First run while (co.next()){ printf ("\t\tresult = %d\n",glocal.result); } sample_costatus(co,"End first run"); // Second run co.restart(); sample_costatus(co,"After restart"); while (co.next()){ printf ("\t\tresult = %d\n",glocal.result); } sample_costatus(co,"End second run"); // Third run, but short co.restart(); sample_costatus(co,"After second restart"); for (int i=0; i<3 && co.next(); i++){ printf ("\t\tresult = %d\n",glocal.result); } co.stop(); sample_costatus(co,"End third run"); // Fourth run, with a restart in the middle co.restart(); sample_costatus(co,"After third restart"); bool restart_done = false; while (co.next()){ printf ("\t\tresult = %d\n",glocal.result); if (glocal.result == 2 && !restart_done){ restart_done = true; co.restart(); } } sample_costatus(co,"End fourth run"); // Now we just start another run, but abort and exits. printf ("\t\tCurrent result=%d\n",glocal.result); // Calling next() after the end has no effect. co.next(); co.restart(); co.next(); printf ("\t\tLast result=%d\n",glocal.result); } // #Specend: // #Specbeg: sample / COROUTINE object performance /* The switch between the parent and the coroutine (in the run functag) is rather fast. On a 2Ghz P4 (thinkpad), this sample runs in 0.15 seconds. */ static void sample_coroutine2() { printf ("Starting\n"); long long now = getnow(); glocal int loop=0; (); for (int i=0; i<1000000; i++){ glocal.loop++; yield(); } while (co.next()); long long end = getnow(); long long diff = end - now; printf ("%d loop in %ld.%06ld seconds\n",glocal.loop ,(long)(diff/1000000),(long)(diff%1000000)); } // #Specend: // #Specbeg: sample / object COROUTINE / recursion /* This sample shows a recursive solution used with co-routine. The co-routine solve the hanoi tour problem. */ struct HTOUR { int nbdisk; int sizes[20]; HTOUR (){ nbdisk = 0; memset (sizes,0,sizeof(sizes)); } void fill (int n){ for (int i=0; i 1){ hanoi (c,tsrc,nbmove-1,tdst,ttmp); } tsrc.nbdisk--; tdst.sizes[tdst.nbdisk] = tsrc.sizes[tsrc.nbdisk]; tdst.nbdisk++; c->yield(); if (nbmove > 1){ hanoi (c,ttmp,nbmove-1,tsrc,tdst); } } static void sample_coroutine3(int nbdisk) { glocal HTOUR t1,t2,t3; glocal.t1.fill (nbdisk); printf ("Starting\n"); glocal.t1.dump(); glocal.t2.dump(); glocal.t3.dump(); (); hanoi (this,glocal.t1,glocal.t1.nbdisk,glocal.t2,glocal.t3); int nbstep=0; while (co.next()){ nbstep++; printf ("State\n"); glocal.t1.dump(); glocal.t2.dump(); glocal.t3.dump(); } printf ("End\n"); glocal.t1.dump(); glocal.t2.dump(); glocal.t3.dump(); printf ("To solve the hanoi tour problem with %d disk\n" "you must perform %d steps.\n",nbdisk,nbstep); } // #Specend: // #Specbeg: sample / object COROUTINE / nested /* This shows two nested COROUTINE */ static void sample_coroutine4() { glocal int val; (); (); for (int i=0; i<5; i++){ glocal.val = i; yield(); } while (co.next()){ glocal.val += 1000; yield(); } while (co.next()){ printf ("result %d\n",glocal.val); } printf ("End\n"); } // #Specend: // #Specbeg: sample / object COROUTINE / ending /* This shows how a co-routine ends even if the parent is not listening anymore. While yield() return -1, the coroutine ignores it. */ static void sample_coroutine5() { glocal int val; (); printf ("Coroutine starting\n"); for (int i=0; i<5; i++){ printf ("coroutine i=%d\n",i); yield(); } printf ("Coroutine ending\n"); co.next(); co.next(); } // #Specend: // #Specbeg: sample / using debug_printf /* debug_printf is a solution to print various debugging information in a controlled way. You simply use it like printf. Application are using the --debug and --debugpath option to select which debugging messages they want. In the following demo, one execute the program like this sample --debug --debugpath key1 debug to get only debugging information relating to context key1. Or one may be more specific sample --debug --debugpath key1/key2 --debugpath key3/key2 debug */ static DEBUG_KEY key1 ("key1","first key"); static DEBUG_KEY key2 ("key2","second key"); static DEBUG_KEY key3 ("key3","third key"); static void sample_debug_f() { DEBUG_CONTEXT ctx(key2); debug_printf ("a sub message\n"); } static void sample_debug() { debug_printf ("This is sample_debug\n"); { DEBUG_CONTEXT ctx(key1); debug_printf ("a message\n"); sample_debug_f(); } sample_debug_f(); { DEBUG_CONTEXT ctx(key3); debug_printf ("a message\n"); sample_debug_f(); } } // #Specend: // #Specbeg: sample / tlmpprogram component int main (int argc, char *argv[]) { (argc, argv); setproginfo ("sample","0.0" ,"This is a sample TLMP program used to demonstrate\n" "various general purposes components, including tlmpprogram\n" "\n" "The program accepts one argument: The name of the component\n" "demonstrate.\n" "\n" "\tcopyfile\n" "\tcoroutine[0-5]\n" "\tdebug\n" "\tloadfile\n" "\tsavefile\n" "\tstreamp\n" "\tSTREAMP\n" "\tstreamp_do\n" "\twalkpopen\n" "\twalkfs\n" ); dialog_clear(); // Main receives the argument vector without the program name if (argc < 1){ // We are allowed to call a functag from another functag // context btw. usage(); }else if (strcmp(argv[0],"copyfile")==0){ sample_copyfile(); }else if (strcmp(argv[0],"loadfile")==0){ sample_loadfile(); }else if (strcmp(argv[0],"savefile")==0){ sample_savefile(); }else if (strcmp(argv[0],"walkfs")==0){ sample_walkfs(); }else if (strcmp(argv[0],"walkpopen")==0){ sample_walkpopen(); }else if (strcmp(argv[0],"streamp")==0){ sample_streamp(); }else if (strcmp(argv[0],"streamp_do")==0){ sample_streamp_do(); }else if (strcmp(argv[0],"STREAMP")==0){ sample_STREAMP(); }else if (strcmp(argv[0],"coroutine0")==0){ sample_coroutine0(); }else if (strcmp(argv[0],"coroutine1")==0){ sample_coroutine1(); }else if (strcmp(argv[0],"coroutine2")==0){ sample_coroutine2(); }else if (strcmp(argv[0],"coroutine3")==0){ if (argc != 2){ fprintf (stderr,"You must supply the number of disk\n"); }else{ sample_coroutine3(atoi(argv[1])); } }else if (strcmp(argv[0],"coroutine4")==0){ sample_coroutine4(); }else if (strcmp(argv[0],"coroutine5")==0){ sample_coroutine5(); }else if (strcmp(argv[0],"debug")==0){ sample_debug(); }else{ usage(); return -1; } printf ("Sample end normally\n"); return 0; return 0; } // #Specend: