Version 16 of TCL interpreter through socket

Updated 2011-04-05 12:38:48 by sergio

sergio: Hi,

we have our application written in Agilent VEE. From our application we want to manage a network tester equipment. The network tester provides us with TCL API functions to control it. Agilent VEE application does not work with TCL at all. So we would like to develop a TCL interpreter that running on localhost. This TCL interpreter is supposed to load network test TCL API package and open a socket to accept API commands or even scripts from our Agilent VEE application.

Any ideas where should we start?

AMG: It's pretty easy to glue an interpreter eval loop to a Tcl TCP/IP socket, but you have to come up with a network protocol that you can use in VEE. I know nothing about VEE, so I'm no help there.

Here's some simple code:

package require Tcl 8.4
proc accept {sock peeraddr peerport} {
    fconfigure $sock -buffering line
    set slave [interp create]
    set script ""
    while {[gets $sock line] != -1} {
        append script $line\n
        if {[info complete $script]} {
            puts $sock [list [catch [list $slave eval $script] result] $result]
            set script ""
        }
    }
    interp delete $slave
    close $sock
}
set serv [socket -server accept 9999]
vwait forever

This program listens on TCP port 9999. When a connection is made, it reads a line of text and adds it to its script buffer. If the script buffer is complete (no mismatched open braces and brackets), it evaluates it in a slave interpreter, and the result is sent back to the client. Only one connection may be made at a time.

The result is formatted as a two-element Tcl list followed by a newline. The first element is 0 if the script executed without error, 1 if there was a problem. The second element is the result, which is either the normal return value or the error message. An empty result is indicated by "0 {}" followed by newline. Newline is a CR/LF pair. For more information on how Tcl lists are formatted, see the Dodekalogue.

Try it out using telnet to see how it behaves.

For more advanced ideas, you can have a look at the comm package (source: [L1 ]).

By the way, you might want a little explanation of how this line works:

puts $sock [list [catch [list $slave eval $script] result] $result]

Let's break it down, step-by-step:

  • $1 = [list $slave eval $script] - Construct a three-element list that will be the first argument to [catch].
    • $slave - Name of the slave interpreter, which is also a command.
    • eval - Interpreter subcommand that means to evaluate the concatenated arguments inside the interpreter, like [interp eval].
    • $script - Script to be executed in the slave interpreter.
  • $2 = [catch $1 result] - Execute the script denoted by $1, store the result in $result, and return 0 or 1 on success or error.
    • $1 - Script to execute.
    • result - Name of the variable into which the result will be stored.
  • $3 = [list $2 $result] - Construct a two-element list which will be sent to the client.
    • $2 - Success/error return from [catch].
    • $result - Result obtained by [catch].
  • puts $sock $3 - Send the two-element list $3 to the client.
    • $sock - Name of the TCP communication channel.
    • $3 - Text to send. A newline will be appended.

sergio: thanks for such a detailed information. That's pretty enough for start.

Actually we want to open socket and keep it with created TCL interpreter as long as it is required.

Let's call it as a connection session to the network tester. The session is over when socket is being closed.

AMG: That's pretty much how it works right now. Each connection gets its own interpreter, so there's fairly minimal interference between connections; they're separate sessions, as you say, and the interpreter is destroyed when the connection is closed. To eliminate interference, use the -safe option to [interp], which prevents access to commands like [cd] that can change application state that is shared between interpreters. Of course, there would still be the restriction on having more than one connection at a time.

If you want the interpreter to not be destroyed, you should create it once globally instead of every time [accept] is called. Either store the name of the interpreter in a global variable, or explicitly give it a name and use that same name to access it inside [accept].

Good luck implementing Tcl lists and quoting inside VEE. You will have to go both ways: write code to construct a Tcl list (formatted as a quoted string) and write code to decipher a Tcl list into whatever VEE's data structures are. If that's too much, Tcl can meet VEE halfway. Make your VEE code send commands to Tcl in a format convenient for VEE, then write Tcl code to translate that into a Tcl list. But you will still need to write VEE code to read Tcl lists. A consequence of Tcl's "everything is a string" design philosophy is that in most cases it's impossible to distinguish between an arbitrary list and a string, so a type tagging mechanism is sometimes required when interfacing with other languages like Python or JavaScript that do distinguish between types.

Some examples of Tcl quoting:

foo\ bar
"foo bar"
{foo bar}

All three lines encode the string f-o-o-space-b-a-r (without the -'s), which could also be viewed as a two-element list containing foo and bar.

Backslash (\) is used to encode special characters: $, ", {, }, [, ], (, ), #, \, :, ;, space, and non-printable. Circumstances dictate which must be quoted, but you can always add a backslash if you're unsure.

sergio: with minor changes it works fine so far. The only concern is I am not getting the same what is printed out in stdout. A TCL script is being executed in the slave interpreter prints out some strings. I expect them to see in result variable. But the result variable contains empty string. On the other hand stdout contains the proper results. Most probably I need to redirect stdout to result variable. In meantime checking why result variable is always empty.

nb Try flushing the channel sergio, or don't use buffering when configuring the socket

sergio For now the result variable does not contain the output of script execution in slave interpreter. The result of execution shows up in stdout only.

Nothing to do with socket yet.

AMG: You're trying to print the result instead of return it. Instead of printing, append it all into a variable then return it at the end of your code. There's some refchan or memchan trickery you can do to make stdout or some other channel be redirected into a variable, or you can replace [puts] with a command that collects data for you to return later. Remember, the code I gave you sends back the result returned by the script.

sergio Using variable and returning it works fine. In this case I can only intercept results produced by my puts. But there still some results are being displayed in stdout. I need to figure out how to collect all results from stdout.