Theo Verelst More Tcl/Tk page


Considering I've done a lot of satisfactory Tcl/Tk script, see my other pages on applications of the language on practical problems, there should be no reason to use it as the basis of some more elaborate applications, such as as the 'Bwise' block editor.

In fact, the portability and flexbility suggest it is a good choice for applications it is fast enough for, and it can be made to interface through standard IO facilities with other modules, for instance written in C, see my Drum Track Generator or  examples.
 

Bwise with direct functionality (latest update)

At this point, I've started to add functionality to bwise blocks, and made them runnable in a connected sense. Each block on the canvas (of which there could be more than one) has one or more functions associated with it, and pins are associated with variables. A global sweeping update function transfers data between blocks, and evaluates the functions associated with each block.

Below, a first example canvas is shown, that actually works the way it suggests. Because the sweep update is dumb, it is repeated as many times as there are blocks, and it even transfers a graph, and does file access, so the result is slow, as is to e expected. It can still easily be waited on, though.

Bwise with almost self-explanatory functionality
Various functions have been added that I will list later, for instance for automatically creating instances of the various types of blocks shown above, for transfering data, accessing block data such as pins, etc.

A function set maintenance aid

Simply put, all functions available to the interpreter are listed, and double-clicking a function name loads it in the statically located editor window (including arguments), where it can be edited, and immedeately updated by a button push. All functions that have been edited this way can be saved as a file containing ordinary tcl commands to define each procedure by another button push.

Example function window.


An interesting addition is the use of the 'vanilla' function, which restricts the displayed functions to all functions that are not avialable in the vanilla function set, sort of making up for the missing 'core image' facilities in languages such as Lisp or Smalltalk that are also used interactively to built both a programs and the environment they run in interactively. All functions that are not in the vanilla set, which is created for instance right after starting up the interpreter, can be listed and saved when edited, ensuring the list of functions stays manageable.

To list all files with procedure defintions in the current directory, in descending order, correctly ordering the interers in the file names:

lsort -dict -decr [glob proc*.tcl]
Using a foreach for each of the return values of this expression with -incr  to source a set of source files will load everthing in in the right order automatically.

I'll do something similar for (global) variables.

More Bwise

I've dedicated a whole page to Bwise already, this time we'll look into the application of graphical respresentation of connected blocks as the canvas for representing data streams.
 

Example Bwise window showing constant, adder,
display and interactive blocks
Each block has a procedure associated with it, and each pin a variable, by concatenating the function name with a dot and the pin name. Once these items are set, a first version of the function 'firelist' executes the blocks given as its arguments for as many times as there are blocks in the list, everytime preceded by a 'transfer' procedure call, which copies the data from output pins over all wires to input pin variables.

The network topology can be determined from:

% netlistout
  {A a Add a} {A a Add3 b} {B b Add b} {B b Add3 c}{Add Sum Add3 a} {Add3 Sum Display In}
Transfering can be done by:
proc transfer { {blocks} } {
   foreach bl $blocks {
       foreach p [block_get_pinnames $bl typeout] {
          foreach w [pin_get_wirenames $bl $p] {
            set a $bl.$p;
             set o [wire_other $w $bl $p];
             set b [lindex $o 0].[lindex $o 1];
            global $a $b
            eval set $b $\{$a\}
          }
      }
   };
   return ok
}
Where the final 'set' command takes into account that variable names should strictly speaking be lists, to work correctly in all circumstances.

Fireing is now done by:
 

proc firelist { {l} } {
   global bfunc t
   set m [llength $l]
   for {set i 0} {$i < $m} {incr i} {
      foreach n $l {
         transfer $l
         set f [blockfunc $n {}]
         eval set fc $$f
         set t $fc
         uplevel #0 eval $t ;
      }
   }
}


This method is dumb, and thus slow, although it does have general applicability (can be linked with my fine grained C multi threading lib, for instance), and the interpreter on my current Pentium 133 isn't too slow for this excess of firing for this example.

The 'display' block has a procedure that updates the test in its block with the string on its input pin, currently the result of additions performed on the constants supplied by block A and B. There is nothing against trace-ing the pin (or internal) variables in these blocks to automatically force an update chain of events for each variable change. Alternatively, external events (over sockets or files) can be fileevent-ed to trigger network activity.

Note the double indirections on lists containing variable names (which may even contain spaces), and that the function associated with a block is executed in the root stack frame, with access to all global resources, which is easiest to experiment with, and allowable since a canvas also has no intrinsic hierarchy except for the naming used, which is this case is guaranteed to be unique.

Next on the wish list are nice input (see below) and update (I already made prototypes pop-up menus) interfaces, and a more intelligent set of fire rules and procedures, for instance fixed trace prototypes for various conditions combined with simple propagation rules.

Note that the whole application is run in perfectly decent tcl, and that blocks interact and are fired in a very direct and flexible way, meaning both the interaction canvas and the data handling are completely portable and have general applicability. For instance, the variable contents passed over wires can consist of any list (or string) value, for instance a complete database field stored as a multilevel list (see below), a number of extreme accuracy, a tcl command (!),  a binary string possibly holding an image, or an indirection  (a pointer directive).

I'm currently starting a library of functions that make sense for interconnections, graph type of operations, and standard blocks. And of course, Bwise is a skeleton containing only the bare necessities, that could do with a lot more facilities, including parametric block prototypes (already standard, see bwise page) with automatic generation of pin variables and default functions.

Using namespaces and seperate interpreters for certain blocks, combined with the easy to use, effective and efficient socket facilities, bwise can represent physically distributed code fragments dynamically interacting under interactive graphical control.
 

A set of database procedures

A database variable is assumed to contain a multilevel list of fields, which contain any number of free form entries, consisting of a name field  and an editable content field. The database variable can easily be stored on disc, and is accessed and searched by list processing functions readily available in tcl.

One database entry is a list of 1 to 3 elements, the field name, the content (optional), and an optional variable name associated with the editable field (that global variable will continuously represent the last value of the field.

To make a database with three entries, with varying content, one could use:
 

set dbvar {
    {
        Name
        {Another field}
        {{Field with content} {The content}}
    }
    {Name Street Phone Description}
    {Name Street Phone Description { {More Comments} {Fill in} } }
}
Below a simiar database has been read, and a entry from it is shown by calling a single function with an element from it:
dbform [lindex $dbvar $number]
Note that the fields are interactive, for instance   set $Name {My Name} would immedeately update the below window.

Example database window
This version of the database detects image fields, and displays images linked with these fields automatically
 

The procedures

As soon as I arrange some more webspace, and have streamlined the experimental procedures a bit, I'll make them available.

I'll for now paste in a version of the function window mentioned above:

# works on PC, Mac and Unix without alteration ...
proc procs_window { } {
           global defaultprocs
           if {[info exists defaultprocs] != 1} {
              set defaultprocs {}
           }
           get_procvanilla
    toplevel .f
    listbox .f.l -height 5 ; pack .f.l -expand n -fill x
    text .f.t -width 20 -height 4 -wrap none ;
    pack .f.t -expand y -fill both
    frame .f.f; pack .f.f -expand n -fill x
    button .f.f.b -text {Update Proc} -command {
       global procs;
       set p [.f.t get 0.0 end];
       eval $p;
       set procs([lindex $p 1]) $p
    }
    pack .f.f.b -side right
    bind .f.l <Double-Button-1> {
       global cf; set cf [selection get];
       .f.t del 0.0 end;
       .f.t insert end "proc $cf \{"
        foreach a [info args $cf] {
           if { [info default $cf $a b] == 1} {
              .f.t insert end " {$a {$b}}" } {
              .f.t insert end " {$a}"
           }
        }
       .f.t insert end " \} \{[info body $cf]\} "
    }
    button .f.f.b2 -text "Refresh List" -command {
       set o {};
       foreach i [info procs] {
          if {[string match {tk*} $i] == 0 &&
                 [string match {tcl*} $i] == 0 &&
                 [lsearch $defaultprocs $i] == -1 } {
                    lappend o $i
                 }
       };
      .f.l del 0 end;
       foreach i [lsort $o] {.f.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 .f.f.bs -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 .f.f.bs -side left
 }
 

# use [info procs] ater just starting tcl, and direct
# the results to 'defaultprocs.tcl'
# before calling this function

proc get_procvanilla { } {
   global defaultprocs ;
   set f [open defaultprocs.tcl r];
   if {$f == {}} {return -1}
   set defaultprocs [ read $f ] ;
   close $f
   return 0
}