Friday, December 18, 2009

Shell Variable and Symbol Processing












































Shell Variable and Symbol Processing


The MicroMonitor CLI provides for two similar
types of token substitution: shell variables and symbols. Shell
variables (strings preceded by $) get their substitution data from the MicroMonitor environment in RAM. Symbols (strings preceded by %)
get their substitution data from a file. From the parsing point of
view, both symbols and shell variables are handled exactly the same way
except that, at the time of substitution, MicroMonitor searches a file
for symbols and the environment for shell variables. Shell variables
are convenient and do not require a file system; however, storage of a
large number of shell variables is a bit impractical because of the RAM
space needed and the volatility of the RAM. On the other hand, if TFS
is included in the monitor build, then a symbol table file can be
downloaded to TFS, and the system can process a large number of symbols
because the data is stored in flash memory. I typically use the symbol
table feature of MicroMonitor for storage of the application’s actual
symbol-to-address list. Putting the symbol-to-address list in a symbol
table makes debugging more convenient when I am not attached to a
symbolic debugger.



Listing 5.5 shows the code in the docommand() function that initiates the substitution.




Listing 5.5: Expanding Shell Variables and Symbols.






/* If there are any instances of a dollar or percent sign within the 
* command line, then expand any shell variables (or symbols) that may
* be present.
*/
if (strpbrk(cmdcpy,"$%")) {
if (expandshellvars(cmdcpy) < 0) {
return(CMD_LINE_ERROR);
}
}














The workhorse for this functionality is the expandshellvars() function (see Listing 5.6). The expandshellvars() function is passed a string that is to be expanded with all shell variables converted. It supports variables of type $VARNAME and ${VARNAME}. The expandshellvars() function also supports the ability to have shell variables embedded within shell variables. For example… ${VAR${NAME}} causes a two-pass expansion in which ${NAME} is evaluated and then ${VARXXX} is evaluated (where XXX is whatever was in the variable ${NAME}).




Listing 5.6: expandshellvars().







int
expandshellvars(char *newstring)
{
char *cp;
int result, cno, ndp;

/* Verify that there is a balanced set of braces in the incoming
* string...
*/
ndp = 0;

if (braceimbalance(newstring,&cno,&ndp)) {
printf("Brace imbalance @ %d%s.\n",
cno,ndp ? " ({ missing $ or %)" : "");
return(-1);
}

/* Process the variable names within braces... */
while((result = processbraces(newstring)) == 1);
if (result == -1)
return(-1);

/* Process dollar signs (left-most first)... */
while((result = processprefixes(newstring)) == 1);
if (result == -1)
return(-1);

/* Cleanup any remaining "\{", "\}" or "\$" strings... */
cp = newstring+1;
while(*cp) {
if (*cp == '{' || *cp == '}' || *cp == '$' || *cp == '%') {
if (*(cp-1) == '\\') {
strcpy(cp-1,cp);
cp -= 2;
}
}
cp++;
}
return(0);
}















The function braceimbalance() in Listing 5.6 does some preprocessing to verify the basic sanity of the command line. The braceimbalance()
function returns zero if there is an even number of braces on the
command line. It takes into account backslash processing (meaning that
a \} or \{ is taken as a literal brace instead of a brace enclosing a shell variable). If braceimbalance() returns non-zero, an error message is printed, and an error (–1) is returned. If braceimbalance() completes successfully, then the rest of the code can at least assume basic brace sanity in the line. If the braces balance, expandshellvars() calls processbraces(). This
function looks into the incoming string for the deepest set of braces
and replaces those braces and the enclosed variable name with the value
stored in the corresponding shell variable. The function returns 1 if a set of braces was processed, and 0 if all braces have been processed. Also, processbraces() returns -1 if some kind of processing error occurs.


The processprefixes() function conducts a similiar looping process. It processes the $ for shell variables and the % for symbols. The processprefixes() function looks for the last $ (or %) in the incoming string and attempts to make a shell variable (or symbol) substitution. If no prefix ($ or %) is found, processprefixes() returns 0.


The bottom while loop in Listing 5.6
removes any backslashes that may have been present to provide immunity
to the previous special-case character processing. The complete code
for each of these functions is on the CD. Because of the nitty-gritty
detail involved in parsing each line, only the top-level details have
been repeated here.






































No comments: