* * * * *

                                 Walkthrough

Now, how did I go about writing 145 lines of bug-free C code to implement the
chunk mechanism? Easy. I broke it down into simpler steps. The main routine,
ChunkProcess() takes five parameters, an output file, the name of the chunk,
the callbacks, number of callbacks, and an arbitrary pointer to data passed
back to the callbacks.

So, basically, we have:

-----[ C ]-----
int (ChunkProcess)(
                   FILE                  *fpout,
                   char                  *name,
                   struct chunk_callback *pcc,
                   size_t                 scc,
                   void                  *data
                 )
{
 char  fname[FILENAME_LEN];
 FILE *fpin;
 int   c;

 assert(fpout != NULL);
 assert(name  != NULL);
 assert(pcc   != NULL);
 assert(scc   >  0);

 sprintf(fname,"chunks/%s",name);
 fpin = fopen(fname,"r");
 if (fpin == NULL)
   return(CHUNKERR_OPEN);

 while(1)
 {
   c = fgetc(fpin);
   if (c == '#')
   {
     c = fgetc(fpin);
     if (c == '#')
     {
       chunk_handle(fpin,fpout,pcc,scc,data);
       continue;
     }

     fputc('#',fpout);
   }

   if (c == EOF) break;
   fputc(c,fpout);
 }

 fclose(fpin);
 return(ERR_OKAY);
}
-----[ END OF LINE ]-----

I just basically look for two consecutive hash marks, and if I find them, I
call chunk_handle() to do the work for me (I should note my convention I'm
using here—StudlyCaps has external linkage, visible to other modules.
lower_case is local to this module). So we now have:

-----[ C ]-----
static void chunk_handle(
                         FILE                  *fpin,
                         FILE                  *fpout,
                         struct chunk_callback *pcc,
                         size_t                 scc,
                         void                  *data
                       )
{
 char  cmdbuf[BUFSIZ];
 char *cmd;
 char *p;

 assert(fpin  != NULL);
 assert(fpout != NULL);
 assert(pcc   != NULL);
 assert(scc   >  0);

 chunk_readcallback(fpin,cmdbuf,BUFSIZ);

 for (
       p = cmdbuf ;
       (cmd = strtok(p," \t\v\r\n")) != NULL ;
       p = NULL
     )
 {
   chunk_docallback(fpout,cmd,pcc,scc,data);
 }
}
-----[ END OF LINE ]-----

chunk_readcallback() reads the text just past the double hash mark to the
following double hash mark. Then using strtok() (easy since it's there, I
know how to use it and I'm not worried about threading issues yet) I break it
up. This allows us to specify multiple callbacks within a single entry and
for each callback, we find it and call the function.

-----[ C ]-----
static void chunk_docallback(
                             FILE                  *fpout,
                             char                  *cmd,
                             struct chunk_callback *pcc,
                             size_t                 scc,
                             void                  *data
                           )
{
 int i;

 assert(fpout != NULL);
 assert(cmd   != NULL);
 assert(pcc   != NULL);
 assert(scc   >  0);

 for (i = 0 ; i < SCC ; i++)
 {
   if (strcmp(cmd,pcc[i].name) == 0)
   {
     (*pcc->callback)(fpout,data);
     return;
   }
 }
 fprintf(fpout,"##processing error - can't find [%s] ##",cmd);
}
-----[ END OF LINE ]-----

Again, since I'm just playing around and want something that works, the
linear scan doesn't scale, but since I'm not planning on having a dozen or
more callbacks, it doesn't hurt. It can be changed easily though since we do
pass in the size of the array and as long as it's noted that the array should
be sorted alphabetically we can later change to a binary search.

I'm not sure if a hash table is the way to go at this point—that might
require a different way of passing in or registering the callbacks, and as it
stands right now, I can use the same templates and have different code for
the callbacks. The chunk “bartitle” which I defined is used all over the
place, and the title itself may not be a date, so the ability to change what
##title## does depending upon what I'm displaying is crucial—I just pass in a
different callback array.

The fprintf() is there for diagnostics—I can leave it out with the effect of
undefined callbacks don't generate any output at all, but there is no
notification of the undefined callback either. I put it in but another way of
handling it might be to print out the callback as found in the text, between
double hashmarks.

And that's it. The code for chunk_readcallback() is easy enough to leave it
as an exercise for the reader, as well as the definition of struct callback.

The trick is just breaking it up into simple pieces.


Email author at [email protected]