package provide boop 1.0 ######################################################################## # # BOOP stands for "basic object oriented programming" -- this is a minimal # object oriented helper that gives you tcl objects, member functions, # object-local storage, memory cleanup all with one tiny ::boop function of # less than 100 lines of tcl. # # The aim of BOOP is to provide a very simple object oriented programming # helper in Tcl, without doing anything fancy or complicated, that requires # a learning curve or causes other problems. If you want a full OOP Tcl # environment, go for "incr Tcl" or "stoop". # # I tried to use stoop, but ran into a number of problems, namely: 1) it # clashes badly with the TclPro debugger, 2) member functions seem to be # wrapped in a silent catch{} statement, making debugging buggy member # functions very difficult and 3) no array support. Incr Tcl was too huge # for my needs, and I wanted an all Tcl-solution, with a minimal learning # curve for my coworkers. I didn't want write OOPy Tcl code that no-one # else would understand. # # In short, I wanted a Tcl OOP helper to be as simple, transparent as # possible, and not to muck with built in commands or cause problems with # the TclPro debugger or confuse people reading my OOPy code. # # Here is an example of using a boop object: # # # A simple member function: # proc test::example {this arg} { # # local member variable # variable ${this}::x # variable ${this}::z # incr x # puts "I am $this, passed '$arg' and x is '$x' and z(y) is '$z(y)'" # } # # # "test" is the class name, "myobject" is the local object name # boop test myobject # # # using the object and member function # $myobject example "argument1" # $myobject example "argument2" # # Boop automatically cleans up the object when it goes out of scope. # ######################################################################## # # AN COMPLETE EXAMPLE # # First, a source code example of using BOOP (FYI, init and deinit # are optional): # # package require boop # # namespace eval test {} # # set test::line_colors { 255 13408767 6684876 10079487 39423 } # # proc test::init {this} { # puts "initializing $this" # namespace eval $this { # variable x 0 # variable z # set z(y) 99 # } # } # # proc test::deinit {this} { # puts "deinitializing $this" # } # # proc test::example {this arg} { # variable ${this}::x # variable ${this}::z # incr x # puts "I am $this, passed '$arg' and x is '$x' and z(y) is '$z(y)'" # } # # proc boop_test {} { # boop test myobject # $myobject example "argument1" # $myobject example "argument2" # } # # boop_test # # Running this code with yield this screen output: # # initializing ::test_1 # I am ::test_1 and was passed 'argument1' and x is '1' and z(y) is '99' # I am ::test_1 and was passed 'argument2' and x is '2' and z(y) is '99' # deinitializing ::test_1 # ######################################################################## # # HOW TO USE BOOP # # First, define functions in a namespace, like so: # # namespace eval test {} # # proc test::example {this arg} { # variable ${this}::x # variable ${this}::z # incr x # puts "I am $this, passed '$arg' and x is '$x' and z(y) is '$z(y)'" # } # # # If you want to initialize some member variables in the namespace for this # object, you can do it in an init function, but this is optional (the # namespace's init function is called automatically at object construction # time) and the objectid is passed in "this". You can also optionally create # a deinit function: # # proc test::init {this} { # namespace eval $this { # variable x 0 # variable z # set z(y) 99 # } # } # # proc test::deinit {this} { # puts "deinitializing $this" # } # # Note how variables for the object are stored in the namespace for the # dynamically created object, allowing easy memory cleanup. # # If you want static member variables, put them in as namespace variables # outside of any proc, like so: # # set test::line_colors { 255 13408767 6684876 10079487 39423 } # # and then refer to then as namespace variables, like so: # # proc test::showcolors {} { puts $test::line_colors } # # Next, create your object with the ::boop command, passing the namespace # name and the variable name that will hold the object, like so: # # boop test myobject # # If your namespace has a namespace::init function it is called automatically # by BOOP at this point. # # Then, just use the proc name (w/o the namespace name) as the 1st parameter, # using the objectid as the proc name, like so: # # $myobject example "argument1" # $myobject example "argument2" # # BOOP will automatically call your namespace::functionname with the namespace # as the first parameter, so be sure that all your member functions take # "this" as their first parameter. # # There is no need to delete your object -- it will clean itself up when its # name goes out of scope. # # # UPVAR note # # If you want to upvar a variable passed to you, use "upvar 2" so as to skip # over the shim function, otherwise you won't get the right variable. You # can get rid of this need for "upvar 2" by changing the boop code to use # "uplevel 1" (as indicated below in the source code comments) but if you do # this the TclPro debugger won't show you the stack frame of the calling # functions, as it doesn't like the uplevel command. If you don't use TclPro, # then this won't matter to you. # # By default, BOOP requires the "upvar 2", so where you used to write: # # proc f {varname} { upvar $varname myvar } # # In BOOP you write: # # proc x::f {this varname} { upvar 2 $varname myvar } # ########### ########### # # This is Boop version 1.0b, released 11/20/2003. # # Boop is a minimal object-oriented interface for Tcl, written entirely # in Tcl, and which plays nicely with debuggers. # # Copyright (C) 2003 John Buckman # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # ########### proc ::boop {class objectname} { # make a number variable for this if {![info exists ${class}::boop_number]} { namespace eval ${class} { variable boop_number 0 } } # keep track of the number of the object, so we don't duplicate variable ${class}::boop_number # increment the number of the object incr ${class}::boop_number # make a namespace for this object, so that member functions can store # variables in the namespace if they want to. set namespacename "::${class}_$boop_number" namespace eval $namespacename {} # place a trace statement on the object var so it can be auto-destroyed. uplevel 1 "set $objectname $namespacename" upvar $objectname myobjectname trace variable myobjectname u ${namespacename}::boop_unset # make a command based on this name of the object, and a delete member function # # note, you can run the function given inside an 'uplevel' command if you like, and if you # do, your function can 'upvar' variables as normal, because then the shim disappears. # However, if you do this, then the TclPro debugger can't show you the any info of the state # of the procs above you, and this is a very useful feature of TclPro. So, if you need # to upvar inside a boop function, use "upvar 2 $x y" to skip over the shim function. set helper " \ proc $namespacename {args} { \n\ set function \[lindex \$args 0\] \n\ set args \[lreplace \$args 0 0\] \n\ set newfunction \[concat ::${class}::\${function} ${namespacename} \$args\] \n\ return \[eval \$newfunction\] #return \[uplevel 1 \$newfunction\] } \n\ # delete member function for the object \n \ proc ${class}::delete {this} { \n \ \ # call the deinit function if it exists \n \ set deinitfunction ::${class}::deinit \n \ if {\[info procs \$deinitfunction\] != \"\"} { \n \ eval \[list \$deinitfunction \$this\] \n \ } \n \ \ # delete the name space, in case it was used for anything \n \ namespace delete ::\$this \n \ \ # remove the object command rename \$this {} \n \ } \n \n \ # destroy object when the variable name that holds it goes out of scope \n \ proc ${namespacename}::boop_unset {name1 name2 op} { \n \ ${namespacename} delete \n \ } \n \ \ " eval $helper set initfunction ${class}::init if {[info procs $initfunction] != ""} { eval [list $initfunction $namespacename] } # return the object id return $namespacename }
Sarnold 22may2005 IMHO the trace add variable should have the following options :
{write unset}
because setting an already existing command make things confuse ; consider the following :
proc ::thing::init {args} { variable myTest boop test myTest $myTest doSomething "Arnold" } ::thing::init ::thing::init
In the code above a memory leak is showed.
Personally, I adapted BOOP with little enhancements to the {*} syntax introduced in Tcl 8.5, and it showed very acceptable perfs.