5.3   The Shell C-Expression Interpreter

The C-expression interpreter is the most common command interface to the Tornado shell. This interpreter can evaluate almost any C expression interactively in the context of the attached target. This includes the ability to use variables and functions whose names are defined in the symbol table. Any command you type is interpreted as a C expression. The shell evaluates that expression and, if the expression so specifies, assigns the result to a variable.

5.3.1   I/O Redirection

Developers often call routines that display data on standard output or accept data from standard input. By default the standard output and input streams are directed to the same window as the Tornado shell. For example, in a default configuration of Tornado, invoking printf( ) from the shell window gives the following display:

-> printf("Hello World\n") 
Hello World! 
value = 13 = 0xd 
-> 

This behavior can be dynamically modified using the Tcl procedure shConfig as follows:

-> ?shConfig SH_GET_TASK_IO off 
-> 
-> printf("Hello World!\n") 
value = 13 = 0xd 
->

The shell reports the printf( ) result, indicating that 13 characters have been printed. The output, however, goes to the target's standard output, not to the shell.

To determine the current configuration, use shConfig. If you issue the command without an argument, all parameters are listed. Use an argument to list only one parameter.

-> ?shConfig SH_GET_TASK_IO 
SH_GET_TASK_IO = off

For more information, see on shConfig, see WindSh Environment Variables.

The standard input and output are only redirected for the function called from WindSh. If this function spawns other tasks, the input and output of the spawned tasks are not redirected to WindSh. To have all IO redirected to WindSh, you can start the target server with the options -C -redirectShell.

5.3.2   Data Types

The most significant difference between the shell C-expression interpreter and a C compiler lies in the way that they handle data types. The shell does not accept any C declaration statements, and no data-type information is available in the symbol table. Instead, an expression's type is determined by the types of its terms.

Unless you use explicit type-casting, the shell makes the following assumptions about data types:

A constant or variable can be treated as a different type than what the shell assumes by explicitly specifying the type with the syntax of C type-casting. Functions that return values other than integers require a slightly different type-casting; see Function Calls. Table 5-10 shows the various data types available in the shell C interpreter, with examples of how they can be set and referenced.

Table 5-10:  Data Types in the Shell C Interpreter


Type
 
Bytes
 
Set Variable
 
Display Variable
 

int
 
4
 
x = 99
 
x 
(int) x
 
long
 
4
 
x = 33 
x = (long)33
 
x 
(long) x
 
short
 
2
 
x = (short)20
 
(short) x
 
char
 
1
 
x = 'A' 
x = (char)65 
x = (char)0x41
 
(char) x
 
double
 
8
 
x = 11.2 
x = (double)11.2
 
(double) x
 
float
 
4
 
x = (float)5.42
 
(float) x
 

Strings, or character arrays, are not treated as separate types in the shell C interpreter. To declare a string, set a variable to a string value.1 For example:

-> ss = "shoe bee doo"

The variable ss is a pointer to the string shoe bee doo. To display ss, enter:

-> d ss

The d( ) command displays memory where ss is pointing.2 You can also use printf( ) to display strings.

The shell places no type restrictions on the application of operators. For example, the shell expression:

 *(70000 + 3 * 16)

evaluates to the 4-byte integer value at memory location 70048.

5.3.3   Lines and Statements

The shell parses and evaluates its input one line at a time. A line may consist of a single shell statement or several shell statements separated by semicolons. A semicolon is not required on a line containing only a single statement. A statement cannot continue on multiple lines.

Shell statements are either C expressions or assignment statements. Either kind of shell statement may call WindSh commands or target routines.

5.3.4   Expressions

Shell expressions consist of literals, symbolic data references, function calls, and the usual C operators.

Literals

The shell interprets the literals in Table 5-11 in the same way as the C compiler, with one addition: the shell also allows hex numbers to be preceded by $ instead of 0x.

Table 5-11:  Literals in the Shell C Interpreter


Literal
 
Example
 

decimal numbers
 
143967
 
octal numbers
 
017734
 
hex numbers
 
0xf3ba or $f3ba
 
floating point numbers
 
666.666
 
character constants
 
'x' and '$'
 
string constants
 
"hello world!!!"
 

Variable References

Shell expressions may contain references to variables whose names have been entered in the system symbol table. Unless a particular type is specified with a variable reference, the variable's value in an expression is the 4-byte value at the memory address obtained from the symbol table. It is an error if an identifier in an expression is not found in the symbol table, except in the case of assignment statements discussed below.

C compilers usually prefix all user-defined identifiers with an underbar, so that myVar is actually in the symbol table as _myVar. The identifier can be entered either way to the shell--the shell searches the symbol table for a match either with or without a prefixed underbar.

You can also access data in memory that does not have a symbolic name in the symbol table, as long as you know its address. To do this, apply the C indirection operator "*" to a constant. For example, *0x10000 refers to the 4-byte integer value at memory address 10000 hex.

Operators

The shell interprets the operators in Table 5-12 in the same way as the C compiler.

Table 5-12:  Operators in the Shell C Interpreter


Operator Type
 
Operators
 

arithmetic
 
+
 
-
 
*
 
/
 
unary -
 
relational
 
==
 
!=
 
<
 
>
 
<=
 
>=
 
shift
 
<<
 
>>
         
logical
 
||
 
&&
 
!
       
bitwise
 
|
 
&
 
~
 
^
     
address and indirection
 
&
 
*
         

The shell assigns the same precedence to the operators as the C compiler. However, unlike the C compiler, the shell always evaluates both sub-expressions of the logical binary operators || and &&.

Function Calls

Shell expressions may contain calls to C functions (or C-compatible functions) whose names have been entered in the system symbol table; they may also contain function calls to WindSh commands that execute on the host.

The shell executes such function calls in tasks spawned for the purpose, with the specified arguments and default task parameters; if the task parameters make a difference, you can call taskSpawn( ) instead of calling functions from the shell directly. The value of a function call is the 4-byte integer value returned by the function. The shell assumes that all functions return integers. If a function returns a value other than an integer, the shell must know the data type being returned before the function is invoked. This requires a slightly unusual syntax because you must cast the function, not its return value. For example:

-> floatVar = ( float ()) funcThatReturnsAFloat (x,y)

The shell can pass up to ten arguments to a function. In fact, the shell always passes exactly ten arguments to every function called, passing values of zero for any arguments not specified. This is harmless because the C function-call protocol handles passing of variable numbers of arguments. However, it allows you to omit trailing arguments of value zero from function calls in shell expressions.

Function calls can be nested. That is, a function call can be an argument to another function call. In the following example, myFunc( ) takes two arguments: the return value from yourFunc( ) and myVal. The shell displays the value of the overall expression, which in this case is the value returned from myFunc( ).

myFunc (yourFunc (yourVal), myVal);

Shell expressions can also contain references to function addresses instead of function invocations. As in C, this is indicated by the absence of parentheses after the function name. Thus the following expression evaluates to the result returned by the function myFunc2( ) plus 4:

4 + myFunc2 ( )

However, the following expression evaluates to the address of myFunc2( ) plus 4:

4 + myFunc2

An important exception to this occurs when the function name is the very first item encountered in a statement. This is discussed in Arguments to Commands.

Shell expressions can also contain calls to functions that do not have a symbolic name in the symbol table, but whose addresses are known to you. To do this, simply supply the address in place of the function name. Thus the following expression calls a parameterless function whose entry point is at address 10000 hex:

0x10000 ()

Subroutines as Commands

Both VxWorks and the Tornado shell itself provide routines that are meant to be called from the shell interactively. You can think of these routines as commands, rather than as subroutines, even though they can also be called with the same syntax as C subroutines (and those that run on the target are in fact subroutines). All the commands discussed in this chapter fall in this category. When you see the word command, you can read subroutine, or vice versa, since their meaning here is identical.

Arguments to Commands

In practice, most statements input to the shell are function calls, often to invoke VxWorks facilities. To simplify this use of the shell, an important exception is allowed to the standard expression syntax required by C. When a function name is the very first item encountered in a shell statement, the parentheses surrounding the function's arguments may be omitted. Thus the following shell statements are synonymous:

-> rename ("oldname", "newname")
-> rename "oldname", "newname"

as are:

-> evtBufferAddress ( )
-> evtBufferAddress

However, note that if you wish to assign the result to a variable, the function call cannot be the first item in the shell statement--thus, the syntactic exception above does not apply. The following captures the address, not the return value, of evtBufferAddress( ):

-> value = evtBufferAddress 

Task References

Most VxWorks routines that take an argument representing a task require a task ID. However, when invoking routines interactively, specifying a task ID can be cumbersome since the ID is an arbitrary and possibly lengthy number.

To accommodate interactive use, shell expressions can reference a task by either task ID or task name. The shell attempts to resolve a task argument to a task ID as follows: if no match is found in the symbol table for a task argument, the shell searches for the argument in the list of active tasks. When it finds a match, it substitutes the task name with its matching task ID. In symbol lookup, symbol names take precedence over task names.

By convention, task names are prefixed with a u for tasks started from the Tornado shell, and with a t for VxWorks tasks started from the target itself. In addition, tasks started from a shell are prefixed by s1, s2, and so on to indicate which shell they were started from. This avoids name conflicts with entries in the symbol table. The names of system tasks and the default task names assigned when tasks are spawned use this convention. For example, tasks spawned with the shell command sp( ) in the first shell opened are given names such as s1u0 and s1u1. Tasks spawned with the second shell opened have names such as s2u0 and s2u1. You are urged to adopt a similar convention for tasks named in your applications.

5.3.5   The "Current" Task and Address

A number of commands, such as c( ), s( ), and ti( ), take a task parameter that may be omitted. If omitted, the current task is used. The l( ) and d( ) commands use the current address if no address is specified. The current task and address are set when:

5.3.6   Assignments

The shell C interpreter accepts assignment statements in the form:

addressExpression = expression

The left side of an expression must evaluate to an addressable entity; that is, a legal C value.

Typing and Assignment

The data type of the left side is determined by the type of the right side. If the right side does not contain any floating-point constants or noninteger type-casts, then the type of the left side will be an integer. The value of the right side of the assignment is put at the address provided by the left side. For example, the following assignment sets the 4-byte integer variable x to 0x1000:

-> x = 0x1000

The following assignment sets the 4-byte integer value at memory address 0x1000 to the current value of x:

-> *0x1000 = x

The following compound assignment adds 300 to the 4-byte integer variable x:

-> x += 300

The following adds 300 to the 4-byte integer at address 0x1000:

-> *0x1000 += 300

The compound assignment operator -=, as well as the increment and decrement operators ++ and --, are also available.

Automatic Creation of New Variables

New variables can be created automatically by assigning a value to an undefined identifier (one not already in the symbol table) with an assignment statement.

When the shell encounters such an assignment, it allocates space for the variable and enters the new identifier in the symbol table along with the address of the newly allocated variable. The new variable is set to the value and type of the right-side expression of the assignment statement. The shell prints a message indicating that a new variable has been allocated and assigned the specified value.

For example, if the identifier fd is not currently in the symbol table, the following statement creates a new variable named fd and assigns to it the result of the function call:

-> fd = open ("file", 0) 

5.3.7   Comments

The shell allows two kinds of comments. First, comments of the form /* */ can be included anywhere on a shell input line. These comments are simply discarded, and the rest of the input line evaluated as usual. Second, any line whose first nonblank character is # is ignored completely. Comments are particularly useful for Tornado shell scripts. See the section Scripts: Redirecting Shell I/O below.

5.3.8   Strings

When the shell encounters a string literal ("") in an expression, it allocates space for the string including the null-byte string terminator. The value of the literal is the address of the string in the newly allocated storage. For instance, the following expression allocates 12 bytes from the target-agent memory pool, enters the string in those 12 bytes (including the null terminator), and assigns the address of the string to x:

-> x = "hello there"

Even when a string literal is not assigned to a symbol, memory is still permanently allocated for it. For example, the following uses 12 bytes of memory that are never freed:

-> printf ("hello there")

If strings were only temporarily allocated, and a string literal were passed to a routine being spawned as a task, then by the time the task executed and attempted to access the string, the shell would have already released--possibly even reused--the temporary storage where the string was held.

This memory, like other memory used by the Tornado tools, comes from the target-agent memory pool; it does not reduce the amount of memory available for application execution (the VxWorks memory pool). The amount of target memory allocated for each of the two memory pools is defined at configuration time; see Scaling the Target Agent.

After extended development sessions in Tornado shells, the cumulative memory used for strings may be noticeable. If this is a problem, restart your target server.

5.3.9   Ambiguity of Arrays and Pointers

In a C expression, a nonsubscripted reference to an array has a special meaning, namely the address of the first element of the array. The shell, to be compatible, should use the address obtained from the symbol table as the value of such a reference, rather than the contents of memory at that address. Unfortunately, the information that the identifier is an array, like all data type information, is not available after compilation. For example, if a module contains the following:

char string [ ] = "hello";

you might be tempted to enter a shell expression like:

(1)    -> printf (string)

While this would be correct in C, the shell will pass the first 4 bytes of the string itself to printf( ), instead of the address of the string. To correct this, the shell expression must explicitly take the address of the identifier:

(2)    -> printf (&string)

To make matters worse, in C if the identifier had been declared a character pointer instead of a character array:

char *string = "hello";

then to a compiler (1) would be correct and (2) would be wrong! This is especially confusing since C allows pointers to be subscripted exactly like arrays, so that the value of string[0] would be "h" in either of the above declarations.

The moral of the story is that array references and pointer references in shell expressions are different from their C counterparts. In particular, array references require an explicit application of the address operator &.

5.3.10   Pointer Arithmetic

While the C language treats pointer arithmetic specially, the shell C interpreter does not, because it treats all non-type-cast variables as 4-byte integers.

In the shell, pointer arithmetic is no different than integer arithmetic. Pointer arithmetic is valid, but it does not take into account the size of the data pointed to. Consider the following example:

-> *(myPtr + 4) = 5

Assume that the value of myPtr is 0x1000. In C, if myPtr is a pointer to a type char, this would put the value 5 in the byte at address at 0x1004. If myPtr is a pointer to a 4-byte integer, the 4-byte value 0x00000005 would go into bytes 0x1010-0x1013. The shell, on the other hand, treats variables as integers, and therefore would put the 4-byte value 0x00000005 in bytes 0x1004-0x1007.

5.3.11   C Interpreter Limitations

Powerful though it is, the C interpreter in the shell is not a complete interpreter for the C language. The following C features are not present in the Tornado shell:

  • Control Structures
  • The shell interprets only C expressions (and comments). The shell does not support C control structures such as if, goto, and switch statements, or do, while, and for loops. Control structures are rarely needed during shell interaction. If you do come across a situation that requires a control structure, you can use the Tcl interface to the shell instead of using its C interpreter directly; see 5.7 Tcl: Shell Interpretation.

  • Compound or Derived Types
  • No compound types (struct or union types) or derived types (typedef) are recognized in the shell C interpreter. You can use CrossWind instead of the shell for interactive debugging, when you need to examine compound or derived types.

  • Macros
  • No C preprocessor macros (or any other preprocessor facilities) are available in the shell. CrossWind does not support preprocessor macros either, but indirect workarounds are available using either the shell or CrossWind. For constant macros, you can define variables in the shell with similar names to the macros. To avoid intrusion into the application symbol table, you can use CrossWind instead; in this case, use CrossWind convenience variables with names corresponding to the desired macros. In either case, you can automate the effort of defining any variables you need repeatedly, by using an initialization script.

    For the first two problems (control structures, or display and manipulation of types that are not supported in the shell), you might also consider writing auxiliary subroutines to provide these services during development; you can call such subroutines at will from the shell, and later omit them from your final application.

    5.3.12   C-Interpreter Primitives

    Table 5-13 lists all the primitives (commands) built into WindSh. Because the shell tries to find a primitive first before attempting to call a target subroutine, it is best to avoid these names in the target code. If you do have a name conflict, however, you can force the shell to call a target routine instead of an identically-named primitive by prefacing the subroutine call with the character @. See Resolving Name Conflicts between Host and Target.

    Table 5-13:  List of WindSh Commands


    agentModeShow( )
    b( )
    bd( )
    bdall( )
    bh( )
    bootChange( )
    browse( )
    c( )
    cd( )
    checkStack( )
    classShow( )
    cplusCtors( )
    cplusDtors( )
    cplusStratShow( )
    cplusXtorSet( )
    cret( )
    d( )
    devs( )
    h( )
    help( )
    hostShow( )
    i( )
    icmpstatShow( )
    ifShow( )
    inetstatShow( )
    intVecShow( )
    iosDevShow( )
    iosDrvShow( )
    iosFdShow( )
     
    ipstatShow( )
    iStrict( )
    l( )
    ld( )
    lkAddr( )
    lkup( )
    ls( )
    m( )
    memPartShow( )
    memShow( )
    moduleIdFigure( )
    moduleShow( )
    mqPxShow( )
    mRegs( )
    msgQShow( )
    period( )
    printErrno( )
    printLogo( )
    pwd( )
    quit( )
    reboot( )
    repeat( )
    routestatShow( )
    s( )
    semPxShow( )
    semShow( )
    shellHistory( )
    shellPromptSet( )
    show( )
     
    smMemPartShow( )
    smMemShow( )
    so( )
    sp( )
    sps( )
    sysResume( )
    sysStatusShow( )
    sysSuspend( )
    taskCreateHookShow( )
    taskDeleteHookShow( )
    taskIdDefault( )
    taskIdFigure( )
    taskRegsShow( )
    taskShow( )
    taskSwitchHookShow( )
    taskWaitShow( )
    tcpstatShow( )
    td( )
    tftpInfoShow( )
    ti( )
    tr( )
    ts( )
    tt( )
    tw( )
    udpstatShow( )
    unld( )
    version( )
    w( )
    wdShow( )
     

    5.3.13   Terminal Control Characters

    When you start a shell from the launcher, the launcher creates a new xterm window for the shell. The terminal control characters available in that window match whatever you are used to in your UNIX shells. You can specify all but one of these control characters (as shown in Table 5-14); see your host documentation for the UNIX command stty.

    When you start the shell from the UNIX command line, it inherits standard input and output (and the associated stty settings) from the parent UNIX shell.

    Table 5-14 lists special terminal characters frequently used for shell control. For more information on the use of these characters, see 5.5 Shell Line Editing and 5.2.7 Interrupting a Shell Command.       

    Table 5-14:  Shell Special Characters


    stty Setting
     
    Common Value
     
    Description
     

    eof
     
    CTRL+D
     
    End shell session.
     
    erase
     
    CTRL+H
     
    Delete a character (backspace).
     
    kill
     
    CTRL+U
     
    Delete an entire line.
     
    intr
     
    CTRL+C
     
    Interrupt a function call.
     
    quit
     
    CTRL+X
     
    Reboot the target, restart server, reattach shell.
     
    stop
     
    CTRL+S
     
    Temporarily suspend output.
     
    start
     
    CTRL+Q
     
    Resume output.
     
    susp
     
    CTRL+Z
     
    Suspend the Tornado shell.
     
    N/A
     
    ESC
     
    Toggle between input mode and edit mode.
    This character is fixed; you cannot change it with stty.
     

    5.3.14   Redirection in the C Interpreter

    The shell provides a redirection mechanism for momentarily reassigning the standard input and standard output file descriptors just for the duration of the parse and evaluation of an input line. The redirection is indicated by the < and > symbols followed by file names, at the very end of an input line. No other syntactic elements may follow the redirection specifications. The redirections are in effect for all subroutine calls on the line.

    For example, the following input line sets standard input to the file named input and standard output to the file named output during the execution of copy( ):

    -> copy < input > output

    If the file to which standard output is redirected does not exist, it is created.

    Ambiguity Between Redirection and C Operators

    There is an ambiguity between redirection specifications and the relational operators less than and greater than. The shell always assumes that an ambiguous use of < or > specifies a redirection rather than a relational operation. Thus the ambiguous input line:

    -> x > y

    writes the value of the variable x to the stream named y, rather than comparing the value of variable x to the value of variable y. However, you can use a semicolon to remove the ambiguity explicitly, because the shell requires that the redirection specification be the last element on a line. Thus the following input lines are unambiguous:

    -> x; > y
    -> x > y;

    The first line prints the value of the variable x to the output stream y. The second line prints on standard output the value of the expression "x greater than y."

    The Nature of Redirection

    The redirection mechanism of the Tornado shell is fundamentally different from that of the Windows command shell, although the syntax and terminology are similar. In the Tornado shell, redirecting input or output affects only a command executed from the shell. In particular, this redirection is not inherited by any tasks started while output is redirected.

    For example, you might be tempted to specify redirection streams when spawning a routine as a task, intending to send the output of printf( ) calls in the new task to an output stream, while leaving the shell's I/O directed at the virtual console. This stratagem does not work. For example, the shell input line:

    -> taskSpawn (...myFunc...) > output

    momentarily redirects the shell standard output during the brief execution of the spawn routine, but does not affect the I/O of the resulting task. To redirect the input or output streams of a particular task, call ioTaskStdSet( ) once the task exists.

    Scripts: Redirecting Shell I/O

    A special case of I/O redirection concerns the I/O of the shell itself; that is, redirection of the streams the shell's input is read from, and its output is written to. The syntax for this is simply the usual redirection specification, on a line that contains no other expressions.

    The typical use of this mechanism is to have the shell read and execute lines from a file. For example, the input lines:

    (1)    -> <startup 
    (2)    -> < /usr/fred/startup

    cause the shell to read and execute the commands in the file startup, either on the current working directory as in (1) or explicitly on the complete path name in (2). If your working directory is /usr/fred, commands (1) and (2) are equivalent.

    Such command files are called scripts. Scripts are processed exactly like input from an interactive terminal. After reaching the end of the script file, the shell returns to processing I/O from the original streams.

    During execution of a script, the shell displays each command as well as any output from that command. You can change this by invoking the shell with the -q option; see the windsh reference entry (online or in C. Tornado Tools Reference).

    An easy way to create a shell script is from a list of commands you have just executed in the shell. The history command h( ) prints a list of the last 20 shell commands. The following creates a file /tmp/script with the current shell history:

    -> h > /tmp/script

    The command numbers must be deleted from this file before using it a shell script.

    Scripts can also be nested. That is, scripts can contain shell input redirections that cause the shell to process other scripts.


    *

    CAUTION: Input and output redirection must refer to files on a host file system. If you have a local file system on your target, files that reside there are available to target-resident subroutines, but not to the shell or to other Tornado tools (unless you export them from the target using NFS, and mount them on your host).


    *

    CAUTION: You should set the WindSh environment variable SH_GET_TASK_IO to off before you use redirection of input from scripts or before you copy and paste blocks of commands to the shell command line. Otherwise commands might be taken as input for a command that preceeds them, and lost.

    C-Interpreter Startup Scripts

    Tornado shell scripts can be especially useful for setting up your working environment. You can run a startup script through the shell C interpreter3 by specifying its name with the -s command-line option to windsh. For example:

    % windsh -s /usr/fred/startup

    Like the .login or .profile files of the UNIX shells, startup scripts can be used for setting system parameters to personal preferences: defining variables, specifying the target's working directory, and so forth. They can also be useful for tailoring the configuration of your system without having to rebuild VxWorks. For example:

    For additional information on initialization scripts, see 5.7 Tcl: Shell Interpretation.


    1:  Memory allocated for string constants is never freed by the shell. See 5.3.8 Strings for more information.

    2:  d( ) is one of the WindSh commands, implemented in Tcl and executing on the host.

    3:  You can also use the -e option to run a Tcl expression at startup, or place Tcl initialization in .wind/windsh.tcl under your home directory. See 5.7.3 Tcl: Tornado Shell lnitialization.