Q: How can I make tclsh event driven?

3 views
Skip to first unread message

David May

unread,
Jul 23, 1998, 3:00:00 AM7/23/98
to
I am still learning about event driven Tcl. I have found that if I execute
vwait I cannot enter Tcl commands at the tclsh prompt. On the other hand
if I do not execute vwait, no events get processed. Can I do both somehow?
I want to run tclsh, type in commands and still process events. The reason
I want to do this is for testing and debugging an event driven
program.

--
David May | mailto:ma...@cygnus.uwa.edu.au | Finger for
| finger:ma...@cygnus.uwa.edu.au | PGP Public Key
| http://cygnus.uwa.edu.au/~mayd |

Nick Maliszewskyj

unread,
Jul 23, 1998, 3:00:00 AM7/23/98
to
There is a way you can do what you want. Create a fileevent on stdin,
read the
input when it's readable, and optionally eval the input if you want to
execute
commands:

#!/usr/bin/tclsh
fileevent stdin readable {
set rawmsg [gets stdin];
puts [eval $rawmsg]
}
vwait forever

This is pretty simplistic and you should build in error handling using
"catch",
but it works well enough.

Hope this helps.
Nick

--
|_ |_ |_ |_________________ Nicholas C. Maliszewskyj
|__ |_ |_ |_ |_ NIST Center for Neutron Research
|_\_ |_ |_ |_ |_ National Institute of
|_ \_ |_ |_ |______ |_ Standards & Technology
|_ \_|_ |_ |_ |_ Bldg 235, Rm E123
|_ \__ |_ |_ |_ Gaithersburg, MD 20899
|_ \_ |__________ |_ ni...@rrdjazz.nist.gov

Paul Duffin

unread,
Jul 23, 1998, 3:00:00 AM7/23/98
to David May
David May wrote:
>
> I am still learning about event driven Tcl. I have found that if I execute
> vwait I cannot enter Tcl commands at the tclsh prompt. On the other hand
> if I do not execute vwait, no events get processed. Can I do both somehow?
> I want to run tclsh, type in commands and still process events. The reason
> I want to do this is for testing and debugging an event driven
> program.
>
> --
> David May | mailto:ma...@cygnus.uwa.edu.au | Finger for
> | finger:ma...@cygnus.uwa.edu.au | PGP Public Key
> | http://cygnus.uwa.edu.au/~mayd |

Either use wish instead of tclsh, or type update when you want the
events to be processed.

--
Paul Duffin
DT/6000 Development Email: pdu...@hursley.ibm.com
IBM UK Laboratories Ltd., Hursley Park nr. Winchester
Internal: 7-246880 International: +44 1962-816880

David May

unread,
Jul 24, 1998, 3:00:00 AM7/24/98
to
Nick Maliszewskyj wrote:
>
> There is a way you can do what you want. Create a fileevent on stdin,
> read the
> input when it's readable, and optionally eval the input if you want to
> execute
> commands:
>
> #!/usr/bin/tclsh
> fileevent stdin readable {
> set rawmsg [gets stdin];
> puts [eval $rawmsg]
> }
> vwait forever
>
> This is pretty simplistic and you should build in error handling using
> "catch",
> but it works well enough.
>
> Hope this helps.
> Nick
>

I did already try this approach before I posted my question and it does
work. But, as you said it is pretty simplistic. The script to be
executed when stdin is readable needs to be more sophisticated to be
useful. For instance you cannot type in multi-line commands. Here is
what
I tried:

proc commandHandler {} {
catch {uplevel #0 [gets stdin]}
puts -nonewline stdout "%"
flush stdout
}

puts -nonewline stdout "%"
flush stdout
fileevent stdin readable [list commandHandler]
vwait events

Paul Duffin

unread,
Jul 24, 1998, 3:00:00 AM7/24/98
to Alexandre Ferrieux
Alexandre Ferrieux wrote:
>
> David May wrote:

> >
> > Nick Maliszewskyj wrote:
> > >
> > > fileevent stdin readable {
> > > set rawmsg [gets stdin];
> > > puts [eval $rawmsg]
> > > }
> > > vwait forever
>
> > you cannot type in multi-line commands. Here is
> > what I tried:
> >
> > proc commandHandler {} {
> > catch {uplevel #0 [gets stdin]}
> > puts -nonewline stdout "%"
> > flush stdout
> > }
> >
> > puts -nonewline stdout "%"
> > flush stdout
> > fileevent stdin readable [list commandHandler]
> > vwait events
>
> Sorry, but how would this solve the multi-line problem ???
>
> A possibility would be to accumulate data up to a well-formed list.
> You also have to handle backslashes at the end of lines (because they
> don't prevent the line from qualifying as a list...).
> Example:
>
> set acc {}
> proc handler {} {
> global acc
> set res [gets stdin line]
> append acc "$line\n"
> if {(![catch {llength $acc}])&&(![string match {*\\} $line])} {
> catch {uplevel #0 $acc}
> set acc {}
> }
> if {$res<0} exit
> }
> fileevent stdin readable handler
> vwait forever
>
> -Alex

What you are trying to implement is the interactive bit of wish so I
would suggest that you look and see how wish does it. Look in StdinProc
in tkMain.c. It uses [info complete $line] to see if the command is
complete before it executes it.

Donald G Porter

unread,
Jul 24, 1998, 3:00:00 AM7/24/98
to
In article <35B7F58B...@allsolutions.com.au>,

David May <Davi...@allsolutions.com.au> wrote:
> I did already try this approach before I posted my question and it does
> work. But, as you said it is pretty simplistic. The script to be
> executed when stdin is readable needs to be more sophisticated to be
> useful. For instance you cannot type in multi-line commands.

Test the input line with [info complete] before passing it to [uplevel].

--
| Don Porter, D.Sc. Mathematical and Computational Sciences Division |
| donald...@nist.gov Information Technology Laboratory |
| http://math.nist.gov/mcsd/Staff/DPorter/ NIST |
|______________________________________________________________________|

Victor Wagner

unread,
Jul 24, 1998, 3:00:00 AM7/24/98
to
Nick Maliszewskyj (ni...@nist.gov) wrote:
: There is a way you can do what you want. Create a fileevent on stdin,
: read the
: input when it's readable, and optionally eval the input if you want to
: execute
: commands:

: #!/usr/bin/tclsh
: fileevent stdin readable {


: set rawmsg [gets stdin];
: puts [eval $rawmsg]
: }
: vwait forever

: This is pretty simplistic and you should build in error handling using
: "catch",

And checking for multiline statements using [info complete $rawmsg]:

fileevent stdin readable {
append rawmsg [gets stdin] "\n"
if [info complete $rawmsg {
catch $rawmsg result
puts $result
unset rawmsg
}
}


: but it works well enough.

Note also Tkcon by J. Hobbs
http://www.cs.uoregon/edu/research/tcl

: Hope this helps.
: Nick

: David May wrote:
: >
: > I am still learning about event driven Tcl. I have found that if I execute
: > vwait I cannot enter Tcl commands at the tclsh prompt. On the other hand
: > if I do not execute vwait, no events get processed. Can I do both somehow?
: > I want to run tclsh, type in commands and still process events. The reason
: > I want to do this is for testing and debugging an event driven
: > program.
: >
: > --
: > David May | mailto:ma...@cygnus.uwa.edu.au | Finger for
: > | finger:ma...@cygnus.uwa.edu.au | PGP Public Key
: > | http://cygnus.uwa.edu.au/~mayd |

: --

: |_ |_ |_ |_________________ Nicholas C. Maliszewskyj
: |__ |_ |_ |_ |_ NIST Center for Neutron Research
: |_\_ |_ |_ |_ |_ National Institute of
: |_ \_ |_ |_ |______ |_ Standards & Technology
: |_ \_|_ |_ |_ |_ Bldg 235, Rm E123
: |_ \__ |_ |_ |_ Gaithersburg, MD 20899
: |_ \_ |__________ |_ ni...@rrdjazz.nist.gov

--
--------------------------------------------------------
I have tin news and pine mail...
Victor Wagner @ home = vitus @ orc . ru

Martin Shepherd

unread,
Jul 26, 1998, 3:00:00 AM7/26/98
to

Alexandre Ferrieux <alexandre...@cnet.francetelecom.fr> writes:
> David May wrote:
> > Nick Maliszewskyj wrote:
> > >...
>...

> Sorry, but how would this solve the multi-line problem ???
>
> A possibility would be to accumulate data up to a well-formed list.

The [info complete $cmd] command was clearly designed for this
purpose. Below you will find a script that I wrote a while ago that
exploits this to add interactive event handling to Tcl. It uses
namespaces so you will need tcl8.0 or later. After sourcing the
following code, start the event handler by typing:

HandleEvents::start

The Tcl prompt will change from % to $, to provide an indication that
the event hander is active. If subsequently you want to abort the
event handler, either type your end-of-file character (Control-D under
Unix), or type:

HandleEvents::stop

The prompt will revert to %.

Note that this code configures itself differently depending on whether
it is run interactively or from a non-interactive script. When used
non-interactively (ie. when ::tcl_interactive is 0) it doesn't add
stdin as an input source for typed commands.

---Cut here------------------------------------------------------------
namespace eval HandleEvents {
variable stop_now 1 ;# This is set to 1 to abort the event handler.
variable user_cmd {} ;# The latest partially read command input string.

# Execute the following command to start the event handler.

proc start {} {

# Any subsequent write to the stop_now namespace variable will
# abort the event loop.

variable stop_now 0

# Should we expect user input?

if { $::tcl_interactive } {

# prompt_user prompts for user input on stdin and arranges for
# read_user_cmd to asynchronously accumulate this input into
# the user_cmd variable until a complete command has been read.

proc prompt_user {} {
variable user_cmd {}
fileevent stdin readable HandleEvents::read_user_cmd
puts -nonewline "$ "
flush stdout
}

# Add the latest line from stdin to user_cmd. If the result
# is a complete command, evaluate it, display its output (if any),
# and prompt for the next command.

proc read_user_cmd {} {

# Append the latest input line to user_cmd, being careful to
# restore the newline that "gets" discards.

variable user_cmd
set user_cmd "$user_cmd\n[gets stdin]"

# Abort the event loop on end-of-file.

if [eof stdin] {
variable stop_now 1
}

# Once a complete command has been accumulated, evaluate it.

if [info complete $user_cmd] {

# Temporarily disable the input handler to allow commands to
# read stdin.

fileevent stdin readable {}

# Append the command-line in the history list.

history add $user_cmd

# Evaluate the command at global scope and display its output,
# if any.

if [catch {
set result [uplevel #0 $HandleEvents::user_cmd]
if [string length $result] {
puts $result
}
} message] {
if [string length [info commands bgerror]] {
bgerror $message
} else {
puts $message
}
}

# Prepare for the next command.

prompt_user
}
}

# Prompt for the first line.

prompt_user
}

# Wait for all pending events to complete, (including user input on
# stdin if requested).

if [catch {vwait HandleEvents::stop_now} result] {
set error_info $::errorInfo
}

# Stop reading commands from stdin.

if { $::tcl_interactive } {
fileevent stdin readable {}
}

# Report vwait errors.

if {[info exists error_info]} {
error "Event handler aborted" $error_info
}
return {}
}

# The following procedure aborts the event handler.

proc stop {} {
variable stop_now 1
}
}
---Cut here------------------------------------------------------------

Martin Shepherd (m...@astro.caltech.edu)

David May

unread,
Jul 28, 1998, 3:00:00 AM7/28/98
to
Victor Wagner wrote:
>
> : This is pretty simplistic and you should build in error handling using
> : "catch",
>
> And checking for multiline statements using [info complete $rawmsg]:
>
> fileevent stdin readable {
> append rawmsg [gets stdin] "\n"
> if [info complete $rawmsg {
> catch $rawmsg result
> puts $result
> unset rawmsg
> }
> }
>
> : but it works well enough.

Yes. The "info complete" does the trick. I also discovered, from the man
pages, the "tcl_prompt1" and "tcl_prompt2" variables which I need to
handle if I want to mimic the tcl shell as closely as possible. It is a
shame that I cannot use the features from Tk, but a pure Tcl
solution works just fine - and was much simpler than I expected.

Reply all
Reply to author
Forward
0 new messages