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.
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.
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.Bwise with almost self-explanatory functionality
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.
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.Example Bwise window showing constant, adder,
display and interactive blocks
The network topology can be determined from:
% netlistoutTransfering can be done by:
{A a Add a} {A a Add3 b} {B b Add b} {B b Add3 c}{Add Sum Add3 a} {Add3 Sum Display In}
proc transfer { {blocks} } {Where the final 'set' command takes into account that variable names should strictly speaking be lists, to work correctly in all circumstances.
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
}
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.
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 {Below a simiar database has been read, and a entry from it is shown by calling a single function with an element from it:
{
Name
{Another field}
{{Field with content} {The content}}
}
{Name Street Phone Description}
{Name Street Phone Description { {More Comments} {Fill in} } }
}
dbform [lindex $dbvar $number]Note that the fields are interactive, for instance set $Name {My Name} would immedeately update the below window.
This version of the database detects image fields, and displays images linked with these fields automaticallyExample database window
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
}