Version 5 of BOOP

Updated 2003-01-30 19:42:52

# 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.  


 #
 #
 # AN EXAMPLE
 #
 # First, a complete minimal example of using BOOP (FYI, init and deinit
 # are optional):
 # 
 #    source "boop.tcl"
 #
 #    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)'"
 #    }
 #
 #    set myobject [boop test]
 #    $myobject example "argument1"
 #    $myobject example "argument2"
 #    $myobject delete
 #
 # 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, like so:
 #
 #    set myobject [boop test]
 #
 # 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 objectid
 # as the first parameter, so be sure that all your member functions take
 # "this" as their first parameter.
 #
 # Finally, be a good citizen and clean up your object with the "delete" member
 # function, which will first call your namespace::deinit function (if one exists)
 # and then will clean up the object, any variables in the global unnamed array
 # with ($objectid*) in the name, and the namespace.  Call delete like so:
 #
 #    $myobject delete
 #
 #
 ###########
 #
 # This is Boop version alpha .2, released 1/30/2003.
 # Copyright 2003 John Buckman <[email protected]>.
 #
 # This source code is released under the GNU general public license.
 #
 ###########

 proc ::boop {class} {

  # 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 {}

  # make a command based on this name of the object, and a delete member 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 \[uplevel 1 \$newfunction\]
 } \n\

 # delete member function for the object  
 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
  namespace delete ::\$this \n \
  \
  # remove the object command
  rename \$this {} \n \
 } \
  "

  eval $helper

  set initfunction ${class}::init
  if {[info procs $initfunction] != ""} {
      eval [list $initfunction $namespacename]
  }

  # return the object id
  return $namespacename
 }

That's a lot of backslashing going on. Couldn't subst clean this up a bit?