Chapter 22. Expect As Just Another Tcl Extension

In this chapter, I will describe how to use Expect as just another extension to Tcl. You can wrap it together with popular Tcl extensions such as Tcl-DP, TkSteal, and others. There are two basic approaches you can take to doing this. You can add extensions to Expect, or you can add Expect as an extension to another Tcl-based program.

While most of the material in this chapter will be of interest only to C and C++ programmers, I will also mention a couple of differences in how Expect behaves and can be used when it is combined with other Tcl extensions.

Adding Expect To Another Tcl-based Program

This section describes how to add Expect to another Tcl-based program. I will use the tclsh program as an example. tclsh is the “Tcl shell” that comes with Tcl. tclsh comes with no other extensions, but you can use it as a template for creating a Tcl-based program with other extensions.

The Tcl source directory contains the template in tclAppInit.c. Copy this file to a new directory and look inside it at the function Tcl_AppInit. You will find the following lines:

if (Tcl_Init(interp) == TCL_ERROR) {
    return TCL_ERROR;
}

After this line, add code to initialize Expect:

if (Exp_Init(interp) == TCL_ERROR) {
    return TCL_ERROR;
}

You may want to add other extensions as well. Generally, you can add the extension initializations in any order unless they attempt to use the same command names. In that case, the later extensions “win”. The basic Tcl commands are actually created before Tcl_Init; however, it must still appear first. Other XXX_Init functions generally define commands for each extension themselves.

Add a line near the top of the file (anywhere after the include of "tcl.h“) to include the declaration of Exp_Init and other Expect definitions. The include looks like this:

#include "expect_tcl.h"

Now compile the tclAppInit.c file with the following command:

cc -I/usr/local/include tclAppInit.c -L/usr/local/lib 
     -lexpect -ltcl -lm

You may need to adjust this command depending on your installation. The −i flag describes the directory where the include files of Tcl, Expect, and other extensions live. Similarly, the -L flag lists where the libraries are. You can have multiple −i and -L flags. The end of the command names particular libraries. You need the Expect library (-lexpect), the Tcl library (-ltcl), and libraries for any other extensions. Most systems need the math library (-lm) and you may also need others depending on your system. If this does not work, look at the Tcl and Expect Makefiles to see what libraries they use on your system.

If the tclAppInit.c file compiled and linked, the compiler leaves an a.out file in the current directory. It is an executable file that understands Tcl commands, Expect commands, and any other extensions you have defined. You can rename and move this to whatever and wherever you want.

If you are using C++ or if any of the extensions use C++, you will need to make an extra step. You must use C to compile tclAppInit.c and C++ for the final command linking everything together into one executable. Some C++ compilers require that the main function also be compiled with C++. Because the Tcl library supplies the default main, you may need to extract or recreate this and compile it with C++.

It is likely that the idea of a library-based main will be revisited in the future. But in all but the simplest of programs, you are likely to want to create your own main anyway—for example, so that you can handle flag and file arguments appropriately to your application.

Differences Between Expect And The Expect Extension In Another Program

When using tclsh or any other program to which you have added Exp_Init, you may encounter differences between it and Expect.

  • Command-Line Argument Handling

    Expect defines the behavior of the command-line flags such as -c and -d. Any other program is not likely to support these. tclsh supports only the -f flag. Most Tcl programs make other arguments available to the script using the argv and argc variables as Expect does.

  • Signal Handling

    It is possible for other extensions to try and handle signals at the same time that Expect does. However, only one extension can handle the same signal at a time. Multiple extensions may claim to be handling the signal but only one of the signal handlers will be called. Signal definitions should be under control of the user so this should not be a problem.

    The default signal handlers that Expect uses (i.e., SIGINT, SIGTERM) are not automatically established by tclsh.

  • Command Names

    Expect commands that share the same names as commands in another extension are usually suppressed (unless the other extension also suppresses its own definition). For example, if another extension defines "spawn“, it overrides Expect’s spawn.

    To guarantee that you get Expect’s commands, preface them with "exp_“. For example, when using Tk, "send" is Tk’s while "exp_send" is Expect’s. The "exp_" versions are always available, even when not using any other extensions, so you can use them all the time if you do not want to worry about switching back and forth.

    Prefixed versions are not provided for Expect commands that already begin with "exp" (such as expect). For example, there is no such command as "exp_expect“.

  • Exit Handling

    Multiple extensions may attempt to provide exit handlers and similar functionality having to do with the exit command. However, only one exit command can possibly execute.

    Extensions that provide exit commands are unlikely to provide the functionality that Expect does or in the way that Expect does.

    Exit handlers declared with "exit -onexit" can be invoked with "exit -noexit“. The terminal modes are also reset, but unlike a plain "exit“, control is returned so that additional Tcl or other extension commands can be executed.

  • Interpreter Prompting

    When typing commands interactively, Expect processes them with the interpreter command. tclsh has its own interpreter; however, it is not directly callable as a command. Most other extensions do not provide interactive command interpreters.

    Expect’s interpreter is similar to tclsh’s interpreter in most ways. There only significant difference is in prompting. tclsh’s interpreter uses the variables tcl_prompt1 and tcl_prompt2 to name functions that produce prompts. Expect’s interpreter uses the functions prompt1 and prompt2 to generate prompts directly. If you run tclsh you will see Tcl’s prompts, and if you invoke exp_interpreter from tclsh, you will see Expect’s prompts.

  • .rc Files

    By default, Expect reads several .rc files when it begins running. This is described further in Chapter 9 (p. 217). These files are not read when using Expect as an extension in another program.

Adding Extensions To Expect

You can add other extensions to Expect similarly to the way I described adding extensions to tclsh. However, by adding extensions to Expect, you keep aspects of Expect, such as Expect’s command-line argument handling.

The Expect source directory contains the template for Expect in exp_main_exp.c. Copy this file to a new directory and look inside it at the main function. You will find the statements (not necessarily in this order):

if (Tcl_Init(interp) == TCL_ERROR) {
    return TCL_ERROR;
}
if (Exp_Init(interp) == TCL_ERROR) {
    return TCL_ERROR;
}

Most other extensions can be added by calling XXX_Init, where XXX is the extension prefix. The actual call should look similar to the ones for Tcl and Expect.

You can generally put the other XXX_Init calls in any order unless the extensions attempt to use the same command names. In that case, the later extensions “win”. Note that the basic Tcl commands are created before Tcl_Init. Other XXX_Init functions generally define commands for each extension themselves.

Add any include lines near the top of the file (anywhere after the include of "tcl.h“) as appropriate to your extension.

Compile the exp_main_exp.c file with the following command.

cc -I/usr/local/include exp_main_exp.c ... 
      -L/usr/local/lib -lexpect -ltcl -lm

You may need to adjust this command depending on your installation. The −i flag describes the directory where the include files of Tcl, Expect, and other extensions live. Similarly, the -L flag lists where the libraries are. You can have multiple −i and -L flags. The end of the command names particular libraries. You need the Expect library (-lexpect), the Tcl library (-ltcl), and libraries for any other extensions. Replace the "..." with whatever .o files or libraries you need. Most systems need the math library (-lm) and you may also need others depending on your system. If this does not work, look at the Tcl and Expect Makefiles to see what libraries they use on your system.

If the exp_main_exp.c file compiled and linked, the compiler leaves an a.out file in the current directory. It is an executable file that understands Tcl commands, Expect commands, and any other extensions you have defined. You can rename and move this to whatever and wherever you want.

Note that if you are using C++ or any of the extensions use C++, you will need to make an extra step, using C to compile exp_main_exp.c and C++ for the final command which links everything together into one executable.

Adding Extensions To Expectk

Adding extensions to Expectk is very similar to adding them to Expect. This section describes only the differences.

The template exp_main_tk.c should be used instead of exp_main_exp.c.

Linking requires the Tk library, so the compile line should look like this:

cc -I/usr/local/include exp_main_tk.c ... 
      -L/usr/local/lib -lexpect -ltk -ltcl -lX11 -lm

As with adding extensions to Expect, you may need to adjust this command also. If this does not work, look at the Tcl and Expect Makefiles to see what libraries they use on your system.

Creating Script-less Expect Programs

Expect normally uses a script to control its execution. This means that to run an Expect application, you need both an Expect interpreter and a script.

It is possible to combine the script and the interpreter together, producing a single executable that does not depend on any other files. This executable can be copied to new machines. The machines must be binary compatible and the script must make sense on the new machine. For example, programs that are spawned by the script must exist on the new machine. Stand-alone executables that run a particular script are often called compiled, although that is not the usual definition of the word.

Compiled scripts are large. Each one must contain the script plus the executable code for Expect. If you use a single Expect application on a computer, then a compiled script makes sense. If you use more than one Expect application, compiled scripts are just a waste of space. Other than space, there are no significant differences in functionality between compiled scripts and file-based scripts.

To create a compiled Expect script using the exp_main_exp.c template, replace all of the calls to the Expect interpretation phase (such as exp_interpret_cmdfilename) with a call to Tcl_Eval. As an argument to Tcl_Eval, pass the string representing the file contents.

Tcl_Eval(interp,cmdstring);

The string representation of the command must be in writeable memory. One way to guarantee this is to declare it in the following style:

static char cmdstring[] = "spawn prog; expect . .";

Calling Tcl_Eval with a string in read-only memory is a common error. Compare the declaration above with the following entry which allows the string to be placed in read-only memory:

char *cmdstring = "spawn prog; expect . .";   /* WRONG */

Any source statements must be removed from the command string. Some Tcl extensions make substantial use of files for storing Tcl commands. All of these file references must be removed in the same way as the script file itself.

Functions And Variables In The Expect Extension

Writing C and C++ code that uses the Expect extension is similar to writing C code that uses Tcl. For example, you can call Tcl_Eval to execute any Expect or Tcl command. The following statements spawn a telnet process and print the new spawn id. Notice the explicit declaration of telnet_cmd as an array instead of a pointer to a string constant. The array declaration guarantees that the characters are put into writeable memory—a requirement of Tcl_Eval.

char *spawn_id;
char telnet_cmd[] = "spawn telnet";

Tcl_Eval(interp,telnet_cmd);
spawn_id = Tcl_GetVar(interp,"spawn_id",0);
printf("spawn id is %s
",spawn_id);

It is possible to call Expect’s commands directly. However, this is a little harder and there is generally no good reason to do so, so it is not documented here.

A number of functions and variables are explicitly made public with C and C++ interfaces. Including the file expect_tcl.h gains access to these public symbols. They are defined in this section. Most of them are useful for writing your own main customized from Expect or tclsh. The descriptions are brief since most of the functional aspects are described in Chapter 9 (p. 209).

The first group of variables are shared by the Tcl-less Expect library, the Expect extension, and the Expect program. In this chapter, only their use in the Expect extension and program will be described.

Shared Variables

int exp_disconnected;

exp_disconnected is initially set to 0. It is set to 1 if Expect’s disconnect command has been used successfully. Setting exp_disconnected to 1 prevents the disconnect command from being called.

int exp_is_debugging;

exp_is_debugging mirrors the Expect command exp_internal. That is, exp_is_debugging contains the most recent value passed as an argument to exp_internal. Similarly, the exp_internal command reflects the value of exp_is_debugging.

int exp_loguser;

exp_loguser mirrors the Expect command log_user.

Non-Shared Variables and Functions

The remaining functions and variables are specific only to the Expect program and extension.

void (*exp_app_exit)(Tcl_Interp *);

exp_app_exit is a pointer to a function that describes an application-specific handler. The handler is executed after the script-defined exit handler has run. A zero value (the default) indicates that there is no handler.

FILE *exp_cmdfile;

exp_cmdfile is a stream from which Expect reads commands.

char *exp_cmdfilename;

exp_cmdfilename is the name of a file which Expect opens and reads commands from.

int exp_cmdlinecmds;

exp_cmdlinecmds is 1 if Expect has been invoked with Expect (or Tcl) commands on the program command line (using -c for example).

int exp_getpid;

exp_getpid is the process id of the Expect process itself (not of any spawned processes).

int exp_interactive;

exp_interactive is 1 if Expect has been invoked with the -i flag or if no scripted commands were invoked when Expect began execution. exp_interactive is used to control whether Expect starts its interpreter command to interact with the user.

Tcl_Interp *exp_interp;

exp_interp points to an interpreter used when no other is available, such as by a signal handler. exp_interp is automatically set by Exp_Init but may be redefined at any time later.

int exp_tcl_debugger_available;

exp_tcl_debugger_available is 1 if the debugger has been armed, typically by a command-line argument.

char *exp_cook(char *string,int *length);

exp_cook reads its string argument and returns a static buffer containing the string reproduced with newlines replaced by carriage-return linefeed sequences. The primary purpose of this is to allow error messages to be produced without worrying about whether the terminal is in raw mode or cooked mode.

The static buffer is overwritten on the next call to exp_cook.

If the length pointer is valid, it is used as the length of the input string. exp_cook also writes the length of the returned string to *length. If the length pointer is 0, exp_cook uses strlen to compute the length of the string, and exp_cook does not return the length to the caller.

void exp_error(Tcl_Interp *interp,char *fmt,...);

exp_error is a printf-like function that writes the result to interp->result. The caller must still return TCL_ERROR to tell the Tcl interpreter that an error has occurred.

void exp_exit(Tcl_Interp *interp,int status);

exp_exit is comparable to Expect’s exit command. exp_exit calls all the exit handlers (see exp_exit_handlers) and then forces the program to exit with the value given by status.

void exp_exit_handlers(Tcl_Interp *);

exp_exit_handlers calls any script-defined exit handler and then any application-defined exit handler. Lastly, the terminal is reset to its original mode.

void exp_interpret_cmdfile(Tcl_Interp *,FILE *);

exp_interpret_cmdfile reads the given stream and evaluates any commands found.

void exp_interpret_cmdfilename(Tcl_Interp *,char *);

exp_interpret_cmdfilename opens the given file and evaluates any commands found.

void exp_interpret_rcfiles(Tcl_Interp *,int my_rc,int sys_rc);

exp_interpret_rcfiles reads and evaluates the .rc files. If my_rc is zero, then ~/.expect.rc is skipped. If sys_rc is zero, then the system-wide expect.rc file is skipped.

int exp_interpreter(Tcl_Interp *);

exp_interpreter interactively prompts the user for commands and evaluates them.

void exp_parse_argv(Tcl_Interp *,int argc,char **argv);

exp_parse_argv reads the representation of the program command line. Based on what is found on the command line, other variables are initialized, including exp_interactive, exp_cmdfilename, exp_cmdlinecmds, etc. exp_parse_argv also reads and evaluates the .rc files if appropriate.

Exercises

  1. In Chapter 19 (p. 435), I described how it took a long time to load the UNIX dictionary into memory using pure Tcl commands. Design and implement an extension to speed that up.

  2. Calculate the time that it took to solve the previous exercise. Divide that by the time difference it takes between loading the dictionary with vanilla Tcl commands and your new extension. How many times will you have to load the dictionary before you earn back the time you spent on the previous exercise?

  3. The Tcl FAQ lists hundreds of extensions to Tcl. Browse through the FAQ and identify extensions that are useful to you. Download, install, and combine them with Expect.

  4. Create your own new extension. If you feel it is of general interest, post a note to the Tcl newsgroup so that others can try it out.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.140.5