interactive command composer

by Theo Verelst

Image Bwise procwindow1.jpg

LV What is this application? A tool to assist in writing tcl code? A tool to assist in generating tcl code? A tool to browse tcl code? I'm trying to figure out a high level description of what this does.

TV In short, what the title page says, making commands by mouse clicks, and only typing the argument values you want to fill in. That's handy for commands with a lot of possible arguments, where you don't want to keep your finger in the manual to type all the right options, of which often you only want to change a few.

Also, it's sometimes easier to see all the arguments and function which are available (which you already made) to use them without typos, and without having to type long commands all the time. The main reason for the type of thinking is functional (de) composition, where you make a certain total function by repeated and nested calls of smaller functions. The idea of the command line is that it can also become an aid for that, which by this tool is made possible in a graphical way, by generating blocks based on functions (procedures can be used as functions, if assumed they don't store things) which can graphically be connected together.

This is the window which starts to deal with the function (procedures) definition and calling, by clicking for the main part, and which can generate blocks from the commands you formed. I changed the image above, maybe a bit clearer, except it deludes in that here the procedure call which is formed and expected as command itself makes a bwise block. The bottom button 'block' would make a (simpler) block which makes this block when 'fired'.

LV Ah - sort of an intelligent syntax knowledgeable editor. I see.

TV Well eeh, not exactly, it's more listing the things which make up procedures, and maybe indeed graphically editing what you want, and then generate a command or a block. The idea of functional (de) composition can often be maintained or used pretty far in programming or problem solving, and in fact lisp and many other programming languages consist for a big part of calling function like entities which call other function like entities, so its a valid fairly general application of the mathematical idea.

The below procedures generate the window shown here, at least they did for me on windows XP with tcl/tk 8.4.4 .

The meaning of the widget elements is as follows:

The left upper list Contains all procedures defined in tcl (in the main namespace/interpreter) without all procedures starting with tcl or tk or pkg and a few more (which are defined when tcl is started up), unless you loaded packages, then all procedures from there are also listed.

By clicking on a procedure name, its arguments are listed in the right upper list, while also the procedure definition, in correct tcl (but see below) appears in the lower edit window, and the command line contains the name of the procedure.

The Form Command line contains a prefixed call to pro_args, which takes its arguments, and produces a command from the options it is presented with, and the tcl procedures' default argument list, such that when the button is pressed, a command is formed in the shortest form possible given the default arguments, which can be executed from the Execute line by pressing the <execute> button when satisfied with the formed command.

Each time an argument is double clicked from the argument list, it appears in the form command editable line, with the right braces around it, and with the cursor ready to fill in the parameter value. This can be repeated in any order until all arguments needed are filled in, and after that <Form Command> creates a well-formed tcl command with those arguments.

The lower procedure edit window contains the procedure last double clicked on in the upper left procedure list. The double-click simply overrides the contents of the window, regardless of what changes you had made, be sure to <Update Proc> regularly when you edit!

Left next to Save Procs Button, an entry can contain a file name, possibly with full path, where all procedures which have been updated with the <Update Proc> during the session will be saved when the button is pressed.

The Refresh list updates the procedure list to become up to date.

The Update Proc basically sources in (evals) the content from the text window, usually containing a single procedure definition, which at that point becomes the actual tcl procedure definition. While editing, the old procedure stays called.

A new procedure can easily be made by editing an old one, by changing name and what else needs change, and pressing <Update Proc>.

The code below assumes you loaded bwise first, command:

destroy .f

to get rid of the old function window, unless you replace the procedure in the 0.34 source, and re-invoke the procedure to set up the procedure list window by

proc procs_window { } {
    global defaultprocs

    # The procedures which are listed in this list are not shown
    if {[info exists defaultprocs] != 1} {
         set defaultprocs {bgerror history loadvfs unknown}
    #  get_procvanilla
    toplevel .f
    wm title .f "Procedure Window"

    frame .f.fu ; pack .f.fu -expand n -fill x;      # top frame with two scrollable lists

    listbox .f.fu.l -height 5 -yscroll ".f.fu.s set";   # left list
    pack .f.fu.l -expand y -fill x -side left
    scrollbar .f.fu.s -command ".f.fu.l yview"
    pack .f.fu.s -side left -expand n -fill y
    listbox -height 5 -yscroll " set";   # right list
    pack -expand y -fill x -side left
    scrollbar -command " yview"
    pack -side left -expand n -fill y

    frame .f.fe ; pack .f.fe -expand n -fill x ;             # Entries
    proc_entry fargs {set fcom [pro_args [lindex $fcom 0] $fargs]} "Form Command"
    proc_entry fcom {} Execute

    frame .f.ft ; pack .f.ft -expand y -fill both ;         # Text area
    pack .f.ft -expand y -fill both
    text .f.ft.t -width 20 -height 4 -wrap none -yscroll ".f.ft.s set";;
    pack .f.ft.t -expand y -fill both -side left
    scrollbar .f.ft.s -command ".f.ft.t yview"
    pack .f.ft.s -side right -expand n -fill y

    frame .f.f; pack .f.f -expand n -fill x
    button .f.f.b -text {Update Proc} -command {
        global procs;
        set p [.f.ft.t get 0.0 end];
        eval $p;
        set procs([lindex $p 1]) $p
    pack .f.f.b -side right
    bind .f.fu.l <Double-Button-1> {
        global cf; set cf [selection get];
        .f.ft.t del 0.0 end;
        .f.ft.t insert end "proc $cf \{" del 0 end; 
        foreach i [info args $cf] {
   insert end $i

        foreach a [info args $cf] {
            if { [info default $cf $a b] == 1} {
                .f.ft.t insert end " {$a {$b}}" } {
                .f.ft.t insert end " {$a}"
        .f.ft.t insert end " \} \{[info body $cf]\} "
        global fargs fcom
        set fcom $cf
        set fargs "pro_args "
    button .f.f.b2 -text "Refresh List" -command {
        set o {};
                                                # Don't list certain procs
        foreach i [info procs] {
            if {[string match {tk*} $i] == 0 && 
            [string match {tcl*} $i] == 0 &&
            [string match {pkg_*} $i] == 0 &&
            [string match {auto_*} $i] == 0 &&
            [lsearch $defaultprocs $i] == -1 } {
                lappend o $i
        .f.fu.l del 0 end;
        foreach i [lsort $o] {.f.fu.l insert end $i}
    pack .f.f.b2 -side right
    entry .f.f.f -width 15 -textvar procsfile
    pack .f.f.f -side left
    button -text {Save Procs} -command {
        global procsfile procs
        set o {}
        foreach i [lsort [array names procs]] {
            eval append o { $procs($i) } \n 
        set f [open $procsfile w];
        puts $f $o;
        close $f
    pack -side left
    bind <Double-Button-1> {
        append fargs " \{" [selection get] " \{"
        .f.fe.ffargs.e icursor end
        append fargs "\}\} "
        # Some time ago this started to be necessary, I don´t know about the backward
        # compatibility, and I only tested on windows that now again the cursor
        # remains in the entry, at the right place:
        focus .f.fe.ffargs.e
    bind .f.fu.l  <F1> [bind .f.fu.l [bind .f.fu.l ]]
    .f.f.b2 invoke
    .f.ft.t insert end "Use refresh list when you made a new procedure.\n"
    .f.ft.t insert end "Double click a procedure name to make it appear \n"
    .f.ft.t insert end "in the bottom window.\n\n"
    .f.ft.t insert end "After editing it, press Update to resource the proc.\n\n"
    .f.ft.t insert end "There is no extra storage except regular tcl procs,\n"
    .f.ft.t insert end "loading another proc destroys you edits: \nUPDATE FIRST.\n\n"
    .f.ft.t insert end "Save button saves EDITED procs, \nsee filebox entry on the left.\n"
    .f.ft.t insert end "Most Bwise regular windows can be resized."

proc proc_entry {var {command {}} {buttontext Do}} {
    set w .f.fe.f$var
    frame $w ; pack $w -expand yes -fill x
    entry $w.e -textvar $var ; pack $w.e -side left -expand y -fill x
    if {$command == {}} {set command "eval \$$var"}
    button $w.b -command $command -text $buttontext
    pack $w.b -side right -expand n -fill none

In fact, the above 2 procedures can also be used without bwise, by themselves, as long as this one is added:

proc pro_args { {p } {ar }  } {
    set o {}
    set c 0; set maxc -1
    foreach a [info args $p] {
        set m {};
        foreach j $ar {
            if [string match $a [lindex $j 0]] { 
                set m 1 
                set arr [lindex $j 1]
                set maxc $c
        if {$m == {}} {
            if { [info default $p $a b] == 1} {
                  append o " [list $b]" } {
                  append o  " {}"
        } {
            append o " [list $arr]"
        incr c
    set o "$p [lrange $o 0 $maxc]"
    return $o

When Bwise is not loaded, the number of visible procedures which are shown are limited to these 3 procedures themselves, and any other procedures you add, so that it is like a fresh session, where all procedures you make, for instance by clearing the bottom window, writing a new procedure in it, and pressing <Update Proc> to create it. After that press <Refresh List> to get a list including the new procedure.

It seems that the example in Quoting and function arguments works when the procedure is double clicked and executed, though the procedure editor doesn't deal with the first argument right.

Putting the the default argument in position for editing is of course on the to-do list, or as an exercise to the reader.

More importantly, integration with bwise blocks is on the agenda (DV), and certainly a history, maybe integration with console, and a database of argument values.

Manual pages and search possibilities would be nice, and of course I want to make a direct link with bwise blocks, preferably both ways, from tcl function to block decomposition, and vice versa.

KPV nice little tool. One suggestion: how about marking all new procedures say in red. By new I mean procedures loaded or altered after running procs_window.

TV Thanks.

In combination with bwise, I wanted to be able to make a procedure, preferably any procedure into a bwise-block, that is one of those yellow rectangles with short blue lines as pins and a name on a tk canvas, which are held together by tag naming conventions, and can be moved around and connected through wires.

I made a function, which takes a procedure as argument, which makes a block of that procedure, with an 'out' pin for the result, and for each argument, a corresponding input pin, named after the arguments:

proc proc_toblock {procname} {

    set c "$procname"
    foreach i [info args $procname] {
        append c " \$\{$procname.[list $i]\}"
    set ret  [eval pro_args newproc "{{f {set $procname.out \[$c\] }}  \
        {in {[info args $procname]}}  {out {out}}  \
        {x {300}}  {y {200}}  {name $procname} \
    } " ]
puts $ret
    eval $ret
    foreach i [info args $procname] {
        uplevel #0 "info default $procname $i $procname.$i"

    return $procname

That looks short...

After having started bwise, source the procedure, and call it with a self defined (non-tcl builtin) procedure name, for instance

proc_toblock newproc

A bwise block is created with the name of the proc as name, and with block pin variables (made of blockname.pinname) for each argument of the procedure, where the pin variables are initialized with the proc's default argument values.

for the example, using the right or middle mouse pop-up menu on the generated block, and selecting 'date' the following list of block variables is automatically shown by bwise, showing this:

Image Bwise procwindow2.jpg Image Bwise procwindow3.jpg

I'm making a separate page on Automatically generate Bwise blocks from procedures.