| ||
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 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.
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.
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.
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.
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.
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); }
To wrap up the details of tfsscript() , I describe the script runners ability to branch to and return from subroutines: gosub and return .
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).
#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.
[1] Notice that we are discussing the monitor code. The monitor not only provides the application with the ability to use TFS, but the monitor itself uses the TFS API.
| ||