Updated 2012-12-19 02:00:37 by RLE

I came up with this while on vacation, needing a really simple OO system for a growing app at work. Basically it's a wrapper around 'namespace'(object) and 'proc'(method). Methods have their name munged to identify them as such, and are provided an instance data namespace-variable "idata" automagically. There is, as of yet, no method for automatic garbage collection. Just like the Microsoft OO system I cribbed the structure from, you need to ->AddRef and ->Release objects manually, and when the reference count drops to zero, the object destroys itself. For an actual real use of simpleOO, check out Sudoku Solver
 package provide simpleOO 0.3

 namespace eval ClassFactory {
   namespace export new object
   
   ###
   # Request a new instance of object $cname
   # with optional configuration in $args
   ###
   proc new {cname args} {
      set iname [uid $cname]
      interp alias {} $iname-> {} ::${cname}::dispatch $iname
      interp alias {} $iname {} ::${cname}::dispatch $iname
      eval $iname Create $args
      return $iname
   }

   ###
   # Define a new class/object
   ###
   proc object {name body} {
      set preCmd {
         proc _Identify {this} {return %cname}
         proc _isa {this class} {
            upvar #0 ::%cname::cdata cdata
            if {"%cname"==$class} {return 1}
            if {[info exists cdata(inherits)]} {
               ::$cdata(inherits)::_isa $this $class
            } {
               return 0
            }
         }
         proc inherits {name} {
            ::ClassFactory::inherit %cname $name
         }
         proc method {cmd args body} {
            ::ClassFactory::method %cname $cmd $args $body
         }
         proc destroy {name} {
            ::ClassFactory::destroy %cname $name
         }
         proc dispatch {this cmd args} {
            eval ::ClassFactory::dispatch %cname $this $cmd $args
         }
      }
      regsub -all {%cname} $preCmd $name preCmd
      set body "$preCmd ; $body"
      namespace eval ::$name $body
   }
   
   ###
   # inherit,method,destroy must be called only within an 'object' block
   ###
   proc inherit {child parent} {
      set ::${child}::cdata(inherits) $parent
   }
   proc method {cname cmd args body} {
      set args [linsert $args 0 this]
      set preCmd {
         upvar #0 ::%cname::cdata cdata ; upvar #0 ::%cname::$this idata
      }
      regsub -all {%cname} $preCmd $cname preCmd
    
      set body "$preCmd ; $body"
      uplevel 1 proc _$cmd $args [list $body]
      uplevel 1 namespace export _$cmd
   }
   proc destroy {cname iname} {
      interp alias {} $iname-> {}
      interp alias {} $iname {}
      unset ::${cname}::$iname
   }

   ###
   # wrapper for interp-alias, do not bare-call
   ###
   proc dispatch {cname iname cmd args} {
      if {[llength [info commands ::${cname}::_$cmd]]!=1} {
         upvar #0 ::${cname}::cdata cdata
         if {[info exists cdata(inherits)]} {
            set cname $cdata(inherits)
            eval dispatch $cname $iname $cmd $args
         } else {
            error "Method $cmd is undefined in $cname."
         }
      } else {
         eval "::${cname}::_$cmd $iname $args"
      }
   }
   proc uid {cname} {
      set i 0
      while {1} {
         if {[interp alias {} ${cname}$i]=={}} {break}
         incr i
      }
      return ${cname}$i
   }
 }
 namespace import ::ClassFactory::*

 ###
 # IUnknown: Both sample code, AND a mandatory inherit for all objects
 ###
 object IUnknown {
   method AddRef {} {
      incr idata(refCount)
   }
   method Release {} {
      incr idata(refCount) -1
      if {$idata(refCount)<=0} {
         $this Destroy
         return 0
      } else {
         return $idata(refCount)
      }
   }
   method Create {} {
      set idata(refCount) 0
   }
   method Destroy {} {
      destroy $this
   }
 }