Now that the monitor design includes a flash file system and a command line interface, I will extend the system so that a file can execute as a script. Actually, the scripting feature turns out to be a pretty simple addition one that adds a lot of flexibility to MicroMonitor. A single script-running function, with the help of a few script-running-specific commands in MicroMonitors command set, provides a versatile environment for simple script execution. 
   The Script Runner 
  The script runner function (  tfsscript()  ) (see Listing 8.1) treats a file as a list of commands. The function reads each line of the file and executes the line as a command. Some commands affect the script runner directly, for example  goto  ,  gosub  ,  return  , and  exit  . I will discuss these commands in detail later. 
     Listing 8.1:   tfsscript()  .      |   | 
  
  int tfsscript(TFILE *fp, int verbose) {     char    lcpy[CMDLINESIZE];     int     tfd, lnsize;     /* TFS does not support calling a script from within a subroutine. */     if (ReturnToDepth != 0)         return(TFSERR_SCRIPTINSUB);     tfd = tfsopen(fp->name,TFS_RDONLY,0);     if (tfd < 0)         return(tfd);     ReturnToTfd = tfd;          while(1) {         lnsize = tfsgetline(tfd,lcpy,CMDLINESIZE);         if (lnsize == 0)    /* end of file? */             break;         if (lnsize < 0) {             printf("tfsscript(): %s\n",tfserrmsg(lnsize));             break;         }         if ((lcpy[0] == '\r')  (lcpy[0] == '\n')) /* empty line? */             continue;         lcpy[lnsize-1] = 0;         /* Remove the newline */         /* Just in case the goto tag was set outside a script, */         /* clear it now. */         if (ScriptGotoTag) {             free(ScriptGotoTag);             ScriptGotoTag = (char *)0;         }         ScriptExitFlag = 0;         /* Execute the command line: */         tfsDocommand(lcpy,verbose);         /* Check for exit flag.  If set, then in addition to terminating the          * script, clear the return depth here so that the "missing return"          * warning  is not printed.  This is done because there is likely          * to be a subroutine with an exit in it and this should not          * cause a warning.          */         if (ScriptExitFlag) {             ReturnToDepth = 0;             break;         }         /* If ScriptGotoTag is set, then attempt to reposition the line           * pointer to the line that contains the tag.          */         if (ScriptGotoTag) {             int     tlen;             tlen = strlen(ScriptGotoTag);             tfsseek(tfd,0,TFS_BEGIN);             while(1) {                 lnsize = tfsgetline(tfd,lcpy,CMDLINESIZE);                 if (lnsize == 0) {                     printf("Tag '%s' not found\n", ScriptGotoTag+2);                     free(ScriptGotoTag);                     ReturnToDepth = 0;                     ScriptGotoTag = (char *)0;                     tfsclose(tfd,0);                     return(TFS_OKAY);                 }                 if (!strncmp(lcpy,ScriptGotoTag,tlen)) {                     free(ScriptGotoTag);                     ScriptGotoTag = (char *)0;                     break;                 }             }         }     }     tfsclose(tfd,0);     if (ScriptExitFlag & REMOVE_SCRIPT)         tfsunlink(fp->name);     if (ReturnToDepth != 0) {         printf("Warning: '%s' missing return.\n",fp->name);         ReturnToDepth = 0;     }     return(TFS_OKAY); }     |   | 
  
           Initially ignoring a few of the global variables used in Listing 8.1, lets begin with the basics. The  tfsscript()  function starts by opening a TFS file by calling  tfsopen()   [1]  . Assuming the file exists, the script runner then enters a loop that repeats for each line in the file. At the top, the loop calls  tfsgetline()  to retrieve the next line in the file. If the line is empty, the loop immediately skips to the next line; otherwise , the line is passed to the function pointed to by  tfsDocommand  . The  tfsDocommand  variable is a function pointer that is loaded with a pointer to the  docommand()  function by default. (I will explain why this is a loaded function pointer instead of just a normal function call in a later chapter.) Ignoring the  ScriptGotoTag  branch for a moment, it becomes clear that the script runner is not terribly complex. The function steps through each line of the script file and passes that line to the command handler. When the function reaches the end of the file, the function closes and returns. 
   Exit 
  The global variables in  tfsscript()  provide the script runner with the ability to jump to tags (  goto  command) and branch to (  gosub  command) or return from (  return  command) subroutines. At any point in the script, the  exit  command can cause the whole thing to terminate. Each of these commands (  goto  ,  gosub  ,  return  , and  exit  ) execute through  docommand()  . Listing 8.2 shows the  exit  command. 
    Listing 8.2:  The  exit  Command.     int ScriptExitFlag; char *ExitHelp[] = {     "Exit a script",     "-[r]",     "Options:",     " -r   remove script after exit",     0, }; int Exit(int argc, char *argv[]) {     ScriptExitFlag = EXIT_SCRIPT;     if ((argc == 2) && (!strcmp(argv[1],"-r")))         ScriptExitFlag = REMOVE_SCRIPT;     return(CMD_SUCCESS); }           If a line in the script contains the command line  exit  , the above code executes out of  tfsscript()  , and, as a result, the global variable  ScriptExitFlag  is set to  EXIT_SCRIPT  . If the  r  option is present, the  REMOVE_SCRIPT  bit would also be set in the global variable  ScriptExitFlag  . Referring to the  tfsscript()  function of Listing 8.1, notice that after  tfsDocommand()  returns, the code looks to see if the  ScriptExitFlag  variable has been modified. If  ScriptExitFlag  is non-zero , the loop is terminated , and the file is closed. If the  REMOVE_SCRIPT  bit is set in  ScriptExitFlag  , then the script is automatically removed. 
     Goto 
  The  tfsscript()  function also provides one global  ScriptGotoTag  variable. The syntax of the  goto  command is  goto { tagname }  , so the  goto  command sets up the  ScriptGotoTag  (a  char  pointer see Listing 8.3) to point to the tag that was passed from the command line as the parameter of a  goto  . Referring to the  tfsscript()  function (see Listing 8.1), if the  ScriptGotoTag  variable is set after the return from  tfsDocommand()  , the currently opened script is searched for the line that starts with a pound sign, a space, and the tag. For example, the line  goto   TOPOFLOOP  causes tfsscript() to search through the currently running script for the line # TOPOFLOOP. The file pointer is then set to this new location in the script, and execution continues. 
     Listing 8.3:  The  goto  Command.     char    *ScriptGotoTag; /* gototag():  *  Used with tfsscript to allow a command to adjust the pointer into the  *  script that is currently being executed.  It simply populates the  *  "ScriptGotoTag" pointer with the tag that should be branched to next.  */ void gototag(char *tag) {     if (ScriptGotoTag)         free(ScriptGotoTag);     ScriptGotoTag = malloc(strlen(tag)+8);     sprintf(ScriptGotoTag,"# %s",tag); } char *GotoHelp[] = {     "Branch to file tag",     "{tagname}",     0, }; int Goto(int argc, char *argv[]) {     if (argc != 2)         return(-1);     gototag(argv[1]);     return(CMD_SUCCESS); }                 gosub  and  return  
  To wrap up the details of  tfsscript()  , I describe the script runners ability to branch to and return from subroutines:  gosub  and  return  . 
    Listing 8.4:  The  gosub  and  return  Commands.     char *GosubHelp[] = {     "Call a subroutine",     "{tagname}",     0, }; int Gosub(int argc, char *argv[]) {     if (argc != 2)         return(-1);     gosubtag(argv[1]);     return(CMD_SUCCESS); } char *ReturnHelp[] = {     "Return from subroutine",     "",     0, }; int Return(int argc, char *argv[]) {     if (argc != 1)         return(-1);     gosubret(0);     return(CMD_SUCCESS); }           The front ends to each of these commands (Listing 8.4) are essentially identical except that one calls  gosubtag()  and one calls  gosubret()  (Listing 8.5). 
     Listing 8.5:   gosub  and  return  Under the Hood.     #define MAXGOSUBDEPTH   15 static long ReturnToTbl[MAXGOSUBDEPTH+1]; static int  ReturnToDepth, ReturnToTfd; void gosubtag(char *tag) {     if (ReturnToDepth >= MAXGOSUBDEPTH) {         printf("Max return-to depth reached\n");         return;     }     ReturnToTbl[ReturnToDepth] = tfstell(ReturnToTfd);     ReturnToDepth++;     gototag(tag); } void gosubret(char *ignored) {     if (ReturnToDepth <= 0)         printf("Nothing to return to\n");     else {         ReturnToDepth--;         tfsseek(ReturnToTfd, ReturnToTbl[ReturnToDepth], TFS_BEGIN);     } }             The  gosub  command calls  gosubtag()  , which records the location in the file to which the subroutine must return. The next element in the  ReturnToTbl[]  array is populated with the value returned by  tfstell()  . The  ReturnToTbl[]  array is essentially a stack of return locations (in the form of file offsets). Function  gosubtag()  then calls  gototag()  , which I discussed earlier. The  return  command unwinds the calling linkage by calling  gosubret()  . Function  gosubret()  pops the return location from the  ReturnToTbl[]  table and returns to the appropriate point in the file (using  tfsseek()  ). The return stack depth is decreased by one, and a seek to that return point in the file is performed. The maximum call nesting is limited by the size of the  ReturnToTbl[]  table. The  gosubtag()  function tests for calls that exceed this limit.